RE: [Haskell] Per-type function namespaces (was: Data.Set whishes)
The idea that I've been throwing around is to be able to define a separate namespace for each type; a function can either belong in a "global" (default) namespace, or belong in a particular type's namespace. So, in the above example, instead of writing "addToFM fm ...", we could instead associate an 'add' function with the FiniteMap type, so we could write "fm.add ..." instead. Provided that fm's type
is monomorphic, it should be possible to call the 'correct' add function; if we defined another 'add' function that's associated with
Remember, too, that in OO languages the type of 'fm' is usually declared, in advance, by the programmer. In Haskell it isn't. That makes it much harder to figure out which 'add' function is going to be used. Which 'add' function is chosen depends on type type of 'fm'. But the add function that is chosen in turn influences the type of the other arguments. For example, in the call (fm.add foo), the type of 'foo' is influenced by the choice of 'add'. But the type of 'foo' might (by the magic of type inference) affect the type of 'fm'.... In Haskell today, you can at least tell what value is bound to each identifier in the program, *without* first doing type checking. And the binding story, all by itself, is somewhat complicated. The typing story is also (very) complicated. Winding the two into a single indissoluble whole would make it far more complicated still. My nose tells me that this way lies madness. But I've been wrong before. Simon
On Fri, 27 Feb 2004, Simon Peyton-Jones wrote:
The idea that I've been throwing around is to be able to define a separate namespace for each type; a function can either belong in a "global" (default) namespace, or belong in a particular type's namespace. So, in the above example, instead of writing "addToFM fm ...", we could instead associate an 'add' function with the FiniteMap type, so we could write "fm.add ..." instead. Provided that fm's type
is monomorphic, it should be possible to call the 'correct' add function; if we defined another 'add' function that's associated with
Remember, too, that in OO languages the type of 'fm' is usually declared, in advance, by the programmer. In Haskell it isn't. That makes it much harder to figure out which 'add' function is going to be used.
Which 'add' function is chosen depends on type type of 'fm'. But the add function that is chosen in turn influences the type of the other arguments. For example, in the call (fm.add foo), the type of 'foo' is influenced by the choice of 'add'. But the type of 'foo' might (by the magic of type inference) affect the type of 'fm'....
In Haskell today, you can at least tell what value is bound to each identifier in the program, *without* first doing type checking. And the binding story, all by itself, is somewhat complicated. The typing story is also (very) complicated. Winding the two into a single indissoluble whole would make it far more complicated still.
I thought this wasn't the case if there are type classes invovled. What value is "+" bound to in 1 + 1? All I can think is to say that the appropriate value of + is selected based on the types, or to say that the value here is the class member (subsuming several instances). Either way I don't see a method for overloading individual function names having a greatly different story either way. Actually, picking a version of a function (from the versions in scope) based on which type actually works might be useful. It seems to extend the handling of overlapping names in a useful direction again, resolving ambiguity by assuming you meant to write a typeable program. We would probably want some special syntax with the imports to request/flag this behaviour, like "import A; import B; import C; resolve foo". One heuristic would be typechecking with no information on the name(s) and checking that there is a unique way to resolve the ambiguity at each point.
My nose tells me that this way lies madness.
I think the general principle of using types to capture and infer intent is still sound. It would be nice to have ad-hoc overloading also in cases where we don't see a common intent between several functions to capture with a typeclass (intents that we can't capture are arguments for improving the class system). A lot of haskell already looks like madness already anyway :) We just need to find things that look like good madness ;)
But I've been wrong before.
Simon
Brandon
In my humble opionon explicit module prefixes are a feature, which enhance code clarity, and not something you want get rid of using rather complex namespace extensions. However, as Alastair Reid's mail in this thread indicates there are weaknesses in haskell's export mechanism. But these would be far more easy to fix than introducing the suggested per type namespace extension. The present export list is also rather ugly from an aesthetic point of view, clottering the module header. I would love to see an, repeatable, explicit 'export' directive with similar keywords as the import directive. Allowing for example: 'export [all] hiding (...)' with obvious semantics. Per Larsson
On 28/02/2004, at 1:30 AM, Per Larsson wrote:
In my humble opionon explicit module prefixes are a feature, which enhance code clarity, and not something you want get rid of using rather complex namespace extensions. However, as Alastair Reid's mail in this thread indicates there are weaknesses in haskell's export mechanism. But these would be far more easy to fix than introducing the suggested per type namespace extension.
Sorry, I don't buy this. If there is effort made to improve the module system to cope with the problems Alastair mentioned, we still have to write 'import FiniteMap as FM' followed by 'FM.add fm', instead of simply 'fm.add'. We fix the problems Alastair mentioned, but are still left with an less-than-optimal solution. -- % Andre Pang : trust.in.love.to.save
Simon Peyton-Jones wrote:
In Haskell today, you can at least tell what value is bound to each identifier in the program, *without* first doing type checking.
I'm afraid I'm confused. In the following code
data Z data S a
class Card c where c2int:: c -> Int
instance Card Z where c2int _ = 0 instance (Card c) => Card (S c) where c2int _ = 1 + c2int (undefined::c)
foo = c2int (undefined::(S (S (S (S Z)))))
how can one tell the value of foo without first doing the typechecking? Without typechecking, we can't use c2int and can't even construct any meaningful value of class Card. For c2int, the type is the ``value.'' Overlapping instances, polymorphic recursion -- all seem to make the value determination even more uncertain. Andre Pang wrote:
1) now I have to manually declare a class definition for every single function, and I have to declare it in advance before any module defines that function (most serious problem; see below),
However, declaring the instance first requires declaring the type class itself, and that _is_ a problem, because that's exactly what I'm trying to work around. Without 20/20 hindsight, you cannot say with certainty what type signatures a "generic" function (like 'phase' or even 'add') can support, because it's not a generic function,
But in the solution posted previously, for each ad hoc overloadable function, the corresponding class *always* has the same signature: class HasAdd a b | a->b where add:: a->b Therefore, we don't need clairvoyance to define an overloadable function that way. If I need an overloadable function add, I can go ahead and define the above class, and then add an instance. I can do that without knowing all possible overloadable instances of add, present or future. What if somebody else did the same in some other module? If that somebody else followed the conventions, he would introduce exactly the same class declaration. If GHC or its developers could somehow be persuaded to overlook _exact_ duplicate class declarations, then that part of the problem can be solved. The class declaration itself could perhaps be generated by Template Haskell. The discussed solution is quite related to some of the Records proposals (which have been discussed here half a year ago). There too we have the inconvenience of choosing unique names for field labels.
data Person = Person {_pname:: String, _paddress:: String} data Computer = Computer {_cname::[String], _caddress:: Int}
class HasName a b | a->b where name:: a->b class HasAddress a b | a->b where address:: a->b
instance HasName Person String where name = _pname instance HasAddress Person String where address = _paddress instance HasName Computer [String] where name = _cname
instance (HasName n r) => HasName (Maybe n) (Maybe r) where name = fmap name
-- Alas, the following will break the dependency... --instance (Num a) => HasName a String where name = show
-- But the following works: overlapping instances at work instance (HasAddress a b) => HasName a b where name = address
instance HasAddress Int (Int->String) where address x y = show (x+y)
newtype W a = W a instance (Num a) => HasAddress (W a) String where address (W a) = show a
test2 = let p = Person "Anonymous" "N/A" c = Computer ["FQDN","localhost"] 10 in "person named " ++ (name p) ++ " at a computer " ++ (head (name c)) ++ " and another " ++ (show$ name p1) where p1 = (Nothing::Maybe Person)
The first few lines of the code is boilerplate and could be automatically generated. As you can see, we can even handle a limited form of polymorphism, and even do a "hand off". For example, if some thing doesn't have a name but has an address, we can use the address as its name. Alas this ``backtracking'' isn't as general as we might wish. At some point we have to introduce wrappers (like W a above) to hand over the dispatch to another class. It's possible to do the dispatch on a class, but it's a bit too painful. OTH, the wrappers such as 'W' may be considered as an 'alternative view' of an object. By wrapping an object, we can switch its behavior from the main one to an alternative without any run-time penalty.
participants (5)
-
Andre Pang -
Brandon Michael Moore -
oleg@pobox.com -
Per Larsson -
Simon Peyton-Jones