Re: Data.Set whishes
Am Montag, 16. Februar 2004 10:05 schrieb Ketil Malde:
Wolfgang Jeltsch <wolfgang@jeltsch.net> writes:
* subsetOf :: Ord element => Set element -> Set element -> Bool
(Isn't "isSubsetOf" a better name?)
So is "isElementOf". I just said "subsetOf" to be consistent with "elementOf". Well, the naming in the Data.* modules should generally undergo some changes.
Would
x `isSubsetOf` y = x `union` y == y
do, or did you want something more efficient?
It's unefficient if, e.g., the x sets are always very small but so are union, intersect etc. I think, at first a complexity of O(|x| + |y|) would be acceptable so that your definition would be fine. Maybe, the implementation of union, intersect etc. should be changed so that the complexity is more like O(min(|x|,|y|)).
-kzm
Wolfgang
Wolfgang Jeltsch wrote:
Am Montag, 16. Februar 2004 10:05 schrieb Ketil Malde:
Wolfgang Jeltsch <wolfgang@jeltsch.net> writes:
* subsetOf :: Ord element => Set element -> Set element -> Bool
(Isn't "isSubsetOf" a better name?)
So is "isElementOf". I just said "subsetOf" to be consistent with "elementOf". Well, the naming in the Data.* modules should generally undergo some changes.[...]
http://www.haskell.org/hierarchical-modules/libraries/library-design.html Cheers, S.
| http://www.haskell.org/hierarchical-modules/libraries/library-design.html I have always wondered why the module system is not used at all in these conventions. I mean, the function names seem to come straight from the Haskell 1.2 days when there was no module system! What I mean is, instead of: newIORef, writeIORef, readIORef We could have: IORef.new, IORef.write, IORef.read (Or: new, write, read if all we use are IORefs.) And instead of: mapSet, emptySet, ... We have: Set.map, Set.empty, ... This is how Chris does it in Edison. Why isn't this used more? /Koen -- Koen Claessen http://www.cs.chalmers.se/~koen/ Chalmers University of Technology, Gothenburg, Sweden
I think it's because of tradition. Originally Haskell didn't have qualified names, only renaming. (Which, IMHO, was a wrong decision in the original Haskell design.) -- Lennart Koen Claessen wrote:
| http://www.haskell.org/hierarchical-modules/libraries/library-design.html
I have always wondered why the module system is not used at all in these conventions. I mean, the function names seem to come straight from the Haskell 1.2 days when there was no module system!
What I mean is, instead of:
newIORef, writeIORef, readIORef
We could have:
IORef.new, IORef.write, IORef.read
(Or: new, write, read if all we use are IORefs.)
And instead of:
mapSet, emptySet, ...
We have:
Set.map, Set.empty, ...
This is how Chris does it in Edison.
Why isn't this used more?
/Koen
-- Koen Claessen http://www.cs.chalmers.se/~koen/ Chalmers University of Technology, Gothenburg, Sweden
_______________________________________________ Haskell mailing list Haskell@haskell.org http://www.haskell.org/mailman/listinfo/haskell
Koen Claessen wrote:
And instead of:
mapSet, emptySet, ...
We have:
Set.map, Set.empty, ...
This is how Chris does it in Edison.
and Daan Leijen in DData: http://www.cs.uu.nl/~daan/ddata.html Christian (Well, Set.map is actually missing there)
Am Freitag, 20. Februar 2004 10:23 schrieb Koen Claessen:
http://www.haskell.org/hierarchical-modules/libraries/library-design.html
I have always wondered why the module system is not used at all in these conventions. I mean, the function names seem to come straight from the Haskell 1.2 days when there was no module system!
What I mean is, instead of:
newIORef, writeIORef, readIORef
We could have:
IORef.new, IORef.write, IORef.read
(Or: new, write, read if all we use are IORefs.)
And instead of:
mapSet, emptySet, ...
We have:
Set.map, Set.empty, ...
This is how Chris does it in Edison.
Why isn't this used more?
/Koen
Hello, the naming scheme you mention is nice, in my opinion. Alas, it has a problem with hierarchical module names. For example, you cannot write Set.empty but have to write Data.Set.empty instead. As modules get more and more nested, the qualified names get longer and longer. A solution would be if Haskell would allow "partially qualified" names, e.g., you import Data and are able to say "Set.empty" afterwards. Wolfgang
Wolfgang Jeltsch <wolfgang@jeltsch.net> writes:
Am Freitag, 20. Februar 2004 10:23 schrieb Koen Claessen:
http://www.haskell.org/hierarchical-modules/libraries/library-design.html
What I mean is, instead of: newIORef, writeIORef, readIORef
We could have: IORef.new, IORef.write, IORef.read
Alas, it has a problem with hierarchical module names. For example, you cannot write Set.empty but have to write Data.Set.empty instead.
Use module renaming: import Data.Set as Set
A solution would be if Haskell would allow "partially qualified" names, e.g., you import Data and are able to say "Set.empty" afterwards.
There was an optional part of the original proposal for hierarchical module names, that the last segment of the hierarchy be automatically regarded as a renaming. e.g. import X.Y.Z could be treated equivalent to import X.Y.Z import X.Y.Z as Z This has been implemented in nhc98 since 2001. Regards, Malcolm
Koen Claessen <koen@cs.chalmers.se> writes:
And instead of:
mapSet, emptySet, ...
We have:
Set.map, Set.empty, ...
This is how Chris does it in Edison. Why isn't this used more?
One could possibly argue that the right solution is to put the operations in classes? There has from time to time been suggestions and discussion about doing this, but I guess there are significant obstacles -- surely, somebody must have tried to clean this up? Or is using MPTC (which possibly will be necessary?) regarded as too non-standard for a library? -kzm -- If I haven't seen further, it is by standing in the footprints of giants
| One could possibly argue that the right solution is to | put the operations in classes? The problem is that sometimes the type of an operation on a particular data structure is not completely according to the general structure. There might be extra restrictions on the type arguments for example (think of Set.map), or there might be an extra argument required or something. Nothing that stops you from having the same name! Also, I think the existence of a possibly better solution (using type classes) is no argument of having names like mapSet and newIORef all over the place. BTW, I realize that it is not easy to change the names of existing libraries, even if their naming scheme is horribly inconsistent, with Data.FiniteMap and Data.Set as good (?) examples. But there is no reason to make a fresh start when designing the naming scheme *standard* for (new) libraries. Why is the naming scheme standard, described at: http://www.haskell.org/hierarchical-modules/libraries/library-design.html still using Haskell 1.2 naming schemes? Do people simply not like qualified names? | There has from time to time been suggestions and | discussion about doing this, but I guess there are | significant obstacles -- surely, somebody must have tried | to clean this up? I think Chris Okasaki did a nice job and made a good data structure library proposal with Edison (years ago). It uses both the qualified names trick and type classes. Why nobody uses it (or even knows about it) is a mystery to me. (The reason Chris gave himself is that he did not completely populate his data structure library framework and that people rather implement their own data structures then instead of fitting them into his framework.) Hereby, I call for a revival of Edison! :-) Regards, /Koen
Koen Claessen wrote:
[...] Why is the naming scheme standard, described at:
http://www.haskell.org/hierarchical-modules/libraries/library-design.html
still using Haskell 1.2 naming schemes? Do people simply not like qualified names?
I think the reason is simply that SimonM copied the relevant section from some ancient hslibs documentation which I put together aeons ago... :-) Cheers, S.
G'day all. Quoting Koen Claessen <koen@cs.chalmers.se>:
I think Chris Okasaki did a nice job and made a good data structure library proposal with Edison (years ago). It uses both the qualified names trick and type classes. Why nobody uses it (or even knows about it) is a mystery to me.
My personal take on why nobody uses it is that when you have a large library of data structures, all of which support various interfaces, the problem arises as to how you pick which one to use. Without looking, what is the difference between a BraunSeq and a BankersQueue? I've also found it hard to merge data structures which are embedded in a monad with data structures which are not. Getting a consistent interface there is hard. If you can think of a solution to either or both of these problems...
Hereby, I call for a revival of Edison! :-)
...give me your sourceforge ID and I'll add you as a developer. :-) Cheers, Andrew Bromage
I have always wondered why the module system is not used at all in these conventions. I mean, the function names seem to come straight from the Haskell 1.2 days when there was no module system!
I used the module system in this way in the first version of the HGL (http://haskell.org/graphics/). For example, fonts, colours, etc all provided three operations 'create', 'delete' and 'select' instead of 'createFont', 'createColor', etc. If (as was common), you imported several of these modules, you would use 'Font.create', 'Color.create', etc. All seemed very clean. I deliberately switched away from this in the second release because it wasn't working very well. The problem is that most users don't want to have to write: import Font import Color import Window [about 10 such modules in total] they just want to write: import Graphics where the Graphics module imports Font, Color, Window, etc and re-exports them. The problem is that you can't use any of the 'create' functions if you import Graphics since any reference to 'Graphics.create' would be ambiguous. Haskell's module system provides a way for a module to merge multiple modules into one but provides no way to eliminate any ambiguities this may create. If we want to be able to use names like 'create' instead of 'createFont', we need to change the module system. The obvious fix would have some of the flavour of the ML module system where a module can export a structured list of names instead of exporting a flat list of names. -- Alastair Reid
I've had an idea stewing in my head to do with per-type function namespaces, that the current module namespace discussion reminded me about. The problem is that there is a limited namespace for functions, so that if you define a new data type, it is unwise to call functions which work on that data type a very generic name such as 'add'. An example of this is Data.FiniteMap and Data.Set: both data types define a function to add things to their respective data types. addToFM :: Ord key => FiniteMap key elt -> key -> elt -> FiniteMap key elt addToSet :: Ord a => Set a -> a -> Set a So at the moment, many Haskellers will append the type name to the function to indicate that it only works on that particular data type. In this respect, Haskell is at a disadvantage vs most object-oriented languages, because in them, you can write "x.add", and the type system will perform "object-oriented polymorphism" for you and call the correct add method, no matter if x is a FiniteMap or a Set. Writing "addToFM fm ..." or "addToSet set ..." is surely a lot more inconvenient than writing "fm.add" or "set.add", no? 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 the Set type, that will only get called if the 'x' in "x.add" is of type :: Set. So, like OO languages which inherently give separate namespaces to their different objects, here we give separate namespaces to different (monomorphic) types. In this case, if one simply writes "add" instead of "x.add", the compiler throws an error, because there is no 'add' function defined in the default namespace; add is only defined when a programmer writes "x.add" where x :: FiniteMap or x :: Set[1]. There are a number of means by which the x in x.add can be communicated to the actual function: it's similar to the hidden 'self' or 'this' variable that's present when you invoke a method on an object in OO. Perhaps x is passed to the function as its first parameter, or maybe it could be its last parameter, or even an arbitrary parameter (where the parameter it's passed as could be defined in the type signature of the function). Perhaps 'self' or 'this' could be an implicit parameter. Any one of them will work just fine, I think. However, this scheme is only for functions which have such a 'primary' data type to be associated with, such as FiniteMap or Set. For functions which are truly polymorphic (such as ==), you still leave them in the default namespace. Perhaps it's sensible to even make it a requirement that functions in the default namespace must be polymorphic: if they are monomorphic, they are associated with operating on a specific data type, so they should belong in a type-specific namespace. You then still guarantee that such commonly-used polymorphic functions cannot be 'hijacked' to have stupid type signatures; i.e. == is always guaranteed to be :: Eq a -> a -> Bool. Anyhow, feedback is more than welcome; I would certainly welcome this addition if it's feasible. It feels inferior to be typing in 'addToFM foo' all the time when our OO brethren type the simpler and more succinct 'foo.add', especially given that Haskell's type system is far more powerful! 1. I haven't thought hard enough about whether it would be possible to have the same function name in both the 'default' namespace as well as in per-type namespaces, but my gut feeling says it should be OK. -- % Andre Pang : trust.in.love.to.save
I've had an idea stewing in my head to do with per-type function namespaces, that the current module namespace discussion reminded me about. The problem is that there is a limited namespace for functions, so that if you define a new data type, it is unwise to call functions which work on that data type a very generic name such as 'add'. [..] 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.
This feature would seem to be in competition with type classes; could you elaborate on the relative advantages and disadvantages? The type class story has the advantage of being well understood and quite effective, but there are certainly some limitations too. --KW 8-)
On 27/02/2004, at 3:47 AM, Keith Wansbrough wrote:
I've had an idea stewing in my head to do with per-type function namespaces, that the current module namespace discussion reminded me about. The problem is that there is a limited namespace for functions, so that if you define a new data type, it is unwise to call functions which work on that data type a very generic name such as 'add'. [..] 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.
This feature would seem to be in competition with type classes; could you elaborate on the relative advantages and disadvantages? The type class story has the advantage of being well understood and quite effective, but there are certainly some limitations too.
I don't think type classes can solve the problem I'm trying to tackle. As an example of why, check out the types of FiniteMap and Set's 'add' functions: addToFM :: Ord key => FiniteMap key elt -> key -> elt -> FiniteMap key elt addToSet :: Ord a => Set a -> a -> Set a Note that the type of addToFM takes in two parameters (besides the FiniteMap itself): a key and an element, whereas the type of addToSet only takes in one parameter, which is the thing to add. So, how can you come up with a type class which provides a polymorphic 'add' function, considering you don't even know how many parameters each data type's individual add function uses? Even if you could define such a type class (which I don't think is possible), you then have one less function in the namespace to use, which is another problem. For example, say I'm writing the Data.Complex module; there's a function in that module "phase :: RealFloat a => Complex a -> a". So, how do you put this phase function into a type class? Perhaps you could abstract away from the RealFloat and Complex bits, so you have a phase function which is generalised to work over a Num and an arbitrary data type instead; e.g. "class Phase c where phase :: Num a => c a -> a". But what happens if, say, somebody adds a Moon data type, and they want to write a phase function which returns the phase of such a moon? Phases of the moon certainly aren't Nums, nevermind the fact that you probably want to supply your moon phase's function with some sort of date as an extra parameter, which means the Phase type class isn't flexible enough. Type classes are designed to provide a type-consistent interface to functions which perform different behaviour, unifying them as one function like + or == -- but it's designed to work for arbitrary types. What I'm after is an interface for a function which may change depending on a "primary" type it's working with, which is almost the opposite to type classes. -- % Andre Pang : trust.in.love.to.save
ozone@algorithm.com.au writes:
addToFM :: Ord key => FiniteMap key elt -> key -> elt -> FiniteMap key elt
addToSet :: Ord a => Set a -> a -> Set a
So, how can you come up with a type class which provides a polymorphic 'add' function, considering you don't even know how many parameters each data type's individual add function uses?
Why, by chea^H^H^Hurrying, of course: class Collection a b | a -> b where add :: a -> b -> a instance Collection Set a where add = addToSet instance Collection FiniteMap k e where add fm (k,e) = addToFM fm k e But I take your point, this could be hard to do in the general case. E.g. 'delete' would probably only want a key. -kzm -- If I haven't seen further, it is by standing in the footprints of giants
Mr. Ozone wrote: [snip]
So at the moment, many Haskellers will append the type name to the function to indicate that it only works on that particular data type. In this respect, Haskell is at a disadvantage vs most object-oriented languages, because in them, you can write "x.add", and the type system will perform "object-oriented polymorphism" for you and call the correct add method, no matter if x is a FiniteMap or a Set. Writing "addToFM fm ..." or "addToSet set ..." is surely a lot more inconvenient than writing "fm.add" or "set.add", no?
Yes. But, you are refering to overloading, no? And, not subtype polymorphism (which is what I denote with "object-oriented polymorphism")? Just to make things clear in my mind.
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 the Set type, that will only get called if the 'x' in "x.add" is of type :: Set. So, like OO languages which inherently give separate namespaces to their different objects, here we give separate namespaces to different (monomorphic) types. In this case, if one simply writes "add" instead of "x.add", the compiler throws an error, because there is no 'add' function defined in the default namespace; add is only defined when a programmer writes "x.add" where x :: FiniteMap or x :: Set[1].
This overloading by namespace is usually called either ADL (Argument-Dependent Lookup) or Koenig Lookup (especially in C++.) So, you have thought of automatically, but implicitly, introduce a namespace for each data type, and then have Haskell employ Koenig Lookup, to decide which function an expression is refering to? You realize, of course, that "mere" intranamespacial parameter type lookup (regular overloading) would achieve the same effect, without the (implicit) namespaces? The core problem in Haskell is to bypass the generics, i.e., make sure that a certain definition is used for a certain type, or combination of types. This can only be done by class instances, as of now, but there have been discussions of non-class overloading.
There are a number of means by which the x in x.add can be communicated to the actual function: it's similar to the hidden 'self' or 'this' variable that's present when you invoke a method on an object in OO. Perhaps x is passed to the function as its first parameter, or maybe it could be its last parameter, or even an arbitrary parameter (where the parameter it's passed as could be defined in the type signature of the function). Perhaps 'self' or 'this' could be an implicit parameter. Any one of them will work just fine, I think.
Again, I think you are confusing the runtime dispatching subtype polymorpism from overloading. Overloading would do what you want, while the subtype polymorphism could (still) be handled by class, and instances of classes, the Generic Programming way. /David
"David Bergman" <davidb@home.se> writes: | > 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 | > the Set type, that will only get called if the 'x' in "x.add" is of | > type :: Set. So, like OO languages which inherently give separate | > namespaces to their different objects, here we give separate | > namespaces to different | > (monomorphic) types. In this case, if one simply writes "add" instead | > of "x.add", the compiler throws an error, because there is no 'add' | > function defined in the default namespace; add is only defined when a | > programmer writes "x.add" where x :: FiniteMap or x :: | > Set[1]. | | This overloading by namespace is usually called either ADL | (Argument-Dependent Lookup) or Koenig Lookup (especially in C++.) Actually in C++, it is called "argument dependent name lookup", and that is the way the C++ definition text calls it. As Andy Koenig has himself pointed out, he did not invent that rule. He mentionned it when the C++ committee was solving a name look-up problem posed by namespaces to operator functions. That name look-up rule was later generalized to non-operator to cover the function-call syntax -- which is what is most known today and referred to above. This ends my C++ hour on Haskell list :-) -- Gaby
Gabriel wrote:
| This overloading by namespace is usually called either ADL | (Argument-Dependent Lookup) or Koenig Lookup (especially in C++.)
Actually in C++, it is called "argument dependent name lookup", and that is the way the C++ definition text calls it. As Andy Koenig has himself pointed out, he did not invent that rule. He mentionned it when the C++ committee was solving a name look-up problem posed by namespaces to operator functions. That name look-up rule was later generalized to non-operator to cover the function-call syntax -- which is what is most known today and referred to above.
This ends my C++ hour on Haskell list :-)
Yeah! Get back to that dark corner where people solve real problems! ;-) /David
On 27/02/2004, at 9:51 AM, David Bergman wrote:
So at the moment, many Haskellers will append the type name to the function to indicate that it only works on that particular data type. In this respect, Haskell is at a disadvantage vs most object-oriented languages, because in them, you can write "x.add", and the type system will perform "object-oriented polymorphism" for you and call the correct add method, no matter if x is a FiniteMap or a Set. Writing "addToFM fm ..." or "addToSet set ..." is surely a lot more inconvenient than writing "fm.add" or "set.add", no?
Yes. But, you are refering to overloading, no? And, not subtype polymorphism (which is what I denote with "object-oriented polymorphism")? Just to make things clear in my mind.
Yes, what I'm referring to is essentially overloading. I called it "object-oriented polymorphism" because that's typically what OO people call such a thing :). (I should know better to use OO terminology on a Haskell list; won't happen again ...). However, it's form of overloading that Haskell cannot currently handle well with type classes -- Oleg's post proves that you can do it, of course, but that's a (very good) hack rather than a long-term solution.
So, you have thought of automatically, but implicitly, introduce a namespace for each data type, and then have Haskell employ Koenig Lookup, to decide which function an expression is refering to?
It's a bit like Koenig lookup in that it has the same effect, although it's probably easier for the compiler to infer the namespace wanted, since we write "expr.function ..." rather than "function expression ...". Writing "function expression ..." would work too, but then it looks like a standard function call rather than a function call associated with a particular type, and I think that causes more confusion. Long-time Haskell users understand that writing "foo.f" means "use f in namespace foo"; changing around the language semantics to mean that "f foo" now means "use f in namespace foo" would make lots of people rather unhappy :).
You realize, of course, that "mere" intranamespacial parameter type lookup (regular overloading) would achieve the same effect, without the (implicit) namespaces?
I'm not sure what you mean by "intranamespcial parameter type lookup" -- can you explain?
There are a number of means by which the x in x.add can be communicated to the actual function: it's similar to the hidden 'self' or 'this' variable that's present when you invoke a method on an object in OO. Perhaps x is passed to the function as its first parameter, or maybe it could be its last parameter, or even an arbitrary parameter (where the parameter it's passed as could be defined in the type signature of the function). Perhaps 'self' or 'this' could be an implicit parameter. Any one of them will work just fine, I think.
Again, I think you are confusing the runtime dispatching subtype polymorpism from overloading. Overloading would do what you want, while the subtype polymorphism could (still) be handled by class, and instances of classes, the Generic Programming way.
I (think I) understand the difference between dynamic binding vs overloading: here, all I'm after is trying to use the type system to give us a very simple form of overloading (e.g. based on the first argument to a function), that gives us the same effect as a per-type name space. -- % Andre Pang : trust.in.love.to.save
Andre "Ozone" wrote:
On 27/02/2004, at 9:51 AM, David Bergman wrote:
So at the moment, many Haskellers will append the type name to the function to indicate that it only works on that particular data type. In this respect, Haskell is at a disadvantage vs most object-oriented languages, because in them, you can write "x.add", and the type system will perform "object-oriented polymorphism" for you and call the correct add method, no matter if x is a FiniteMap or a Set. Writing "addToFM fm ..." or "addToSet set ..." is surely a lot more inconvenient than writing "fm.add" or "set.add", no?
Yes. But, you are refering to overloading, no? And, not subtype polymorphism (which is what I denote with "object-oriented polymorphism")? Just to make things clear in my mind.
Yes, what I'm referring to is essentially overloading. I called it "object-oriented polymorphism" because that's typically what OO people call such a thing :).
No, "they" do not. What they call polymorphism, we call subype polymorphism or, if we are really hard-core and/or old school, even ad-hoc polymorphism. "They" do not even realize that overloading falls in the category of polymorphism at all...
(I should know better to use OO terminology on a Haskell list; won't happen again ...).
I think it is good that you do. We need that touch of engineering realism sometimes ;-)
However, it's form of overloading that Haskell cannot currently handle well with type classes -- Oleg's post proves that you can do it, of course, but that's a (very good) hack rather than a long-term solution.
I personally use (Haskell) classes for that overloading purpose, but in a sense that Generic Programmers would call concept modelling.
So, you have thought of automatically, but implicitly, introduce a namespace for each data type, and then have Haskell employ Koenig Lookup, to decide which function an expression is refering to?
It's a bit like Koenig lookup in that it has the same effect, although it's probably easier for the compiler to infer the namespace wanted, since we write "expr.function ..." rather than "function expression ...".
Whether it is prefix or postfix should not alter the complexity of the lookup considerably.
Writing "function expression ..." would work too, but then it looks like a standard function call rather than a function call associated with a particular type, and I think that causes more confusion. Long-time Haskell users understand that writing "foo.f" means "use f in namespace foo"; changing around the language semantics to mean that "f foo" now means "use f in namespace foo" would make lots of people rather unhappy :).
I would want it to look as an ordinary function. The single biggest problem with Haskell, in my extremely humble opinion, is the shared namespace for all data type accessors, with which you probably agree. It is what irritated me the most with Entity-Relationship Diagram, that all fields need to have unique name globally. This in contrast to instance variables, methods and general overloading, as often found in OO languages.
You realize, of course, that "mere" intranamespacial parameter type lookup (regular overloading) would achieve the same effect, without the (implicit) namespaces?
I'm not sure what you mean by "intranamespcial parameter type lookup" -- can you explain?
ah, I meant regular overloading, i.e., have a function be identified not by its name, but by its whole signature, including the arity and parameter type(s) [yes, curry, curry...]
There are a number of means by which the x in x.add can be communicated to the actual function: it's similar to the hidden 'self' or 'this' variable that's present when you invoke a method on an object in OO. Perhaps x is passed to the function as its first parameter, or maybe it could be its last parameter, or even an arbitrary parameter (where the parameter it's passed as could be defined in the type signature of the function). Perhaps 'self' or 'this' could be an implicit parameter. Any one of them will work just fine, I think.
Again, I think you are confusing the runtime dispatching subtype polymorpism from overloading. Overloading would do what you want, while the subtype polymorphism could (still) be handled by class, and instances of classes, the Generic Programming way.
I (think I) understand the difference between dynamic binding vs overloading: here, all I'm after is trying to use the type system to give us a very simple form of overloading (e.g. based on the first argument to a function), that gives us the same effect as a per-type name space.
When I read my own response, I know realize that it sounds harsh. Sorry about that. That was not the intention. I also think you understand and appreciate the difference between the two forms of polymorphisms. You touch at one of the core problems of Haskell. Thanks, David
On 28/02/2004, at 3:26 AM, David Bergman wrote:
Writing "function expression ..." would work too, but then it looks like a standard function call rather than a function call associated with a particular type, and I think that causes more confusion. Long-time Haskell users understand that writing "foo.f" means "use f in namespace foo"; changing around the language semantics to mean that "f foo" now means "use f in namespace foo" would make lots of people rather unhappy :).
I would want it to look as an ordinary function.
I don't know if I'd want it to look as an ordinary function, because then it looks and acts exactly the same as every single other function, and it clearly isn't. As an example of why, look at the API for IORef: in my fantasy world, if I were able to write 'readIORef myRef' as 'myRef.read', I definitely do not want a top-level 'read' function to be defined. Not only does it pollute the default namespace unnecessarily, but in this case it actually does conflict with an already-existing read function, so what is the type of read now? read has always been the function that's associated with the Read type class, but the name of 'read' for the IORef accessor clearly also makes sense. They are clearly two different functions, and I would wish for them to stay that way.
The single biggest problem with Haskell, in my extremely humble opinion, is the shared namespace for all data type accessors, with which you probably agree. It is what irritated me the most with Entity-Relationship Diagram, that all fields need to have unique name globally. This in contrast to instance variables, methods and general overloading, as often found in OO languages.
Right, I completely agree that the shared namespace for data type accessors is the core of the problem, which is what led to me discuss the scheme I had in mind. I think the community, too, realises this problem is more than just an annoyance: at the 2003 Haskell Workshop, a new labelled fields (record?) system was being discussed, and there was much nodding when somebody stated that they'd be entirely happy keeping the current labelled fields system except that they wanted to be able to use the same field name for different data types. So, such a system effectively provides individual namespaces per data type, which is similar to my goal. However, because labelled field selectors operate directly on the data type they're defined on, they are unsuitable to be exported as a accessor function to an outside module, because they provide direct access to the internals of the data type rather than abstracting that away via a function. However, what I'm after is exactly the same goal as yours. Thank you for bringing in the name 'accessor', by the way -- it's much nicer than writing 'per-type function' :).
"mere" intranamespacial parameter type lookup (regular overloading) would achieve the same effect, without the (implicit) namespaces?
I'm not sure what you mean by "intranamespcial parameter type lookup" -- can you explain?
ah, I meant regular overloading, i.e., have a function be identified not by its name, but by its whole signature, including the arity and parameter type(s) [yes, curry, curry...]
Sure, overloading can do exactly what we want (and Oleg has proven it with the sample code he's pasted). I am quite happy if type classes are capable of modelling such a scheme, although there must be some support from the compiler so that one doesn't have to manually declare a type class + various instances for each accessor you'd want to write. One thing I am a bit worried about using the current type class mechanism to do such a scheme is that you still clutter the global namespace (see a few paragraphs up about readIORef for why).
I (think I) understand the difference between dynamic binding vs overloading: here, all I'm after is trying to use the type system to give us a very simple form of overloading (e.g. based on the first argument to a function), that gives us the same effect as a per-type name space.
When I read my own response, I know realize that it sounds harsh. Sorry about that. That was not the intention. I also think you understand and appreciate the difference between the two forms of polymorphisms.
No offense taken; if anything, you are making me think harder about the problem I'm trying to solve and whether the way I'm going about it is right (or has been done before).
You touch at one of the core problems of Haskell.
Glad to see somebody else agrees. Oddly enough, the more I discuss this problem, the more I'm convinced that the approach I've suggested is the ideal way to go about it--implementation details being a minor detail, of course ;). What I'm secretly hoping is that the GHC/hugs/HBC people will see what I'm trying to achieve, tell me I'm totally nuts, and then suggest an alternative, much simpler approach which gives us exactly the same goal ... -- % Andre Pang : trust.in.love.to.save
Hello!
So, how can you come up with a type class which provides a polymorphic 'add' function, considering you don't even know how many parameters each data type's individual add function uses?
Very easily: every Haskell function takes only one argument. Always. Ever.
For example, say I'm writing the Data.Complex module; there's a function in that module "phase :: RealFloat a => Complex a -> a". So, how do you put this phase function into a type class? Perhaps you could abstract away from the RealFloat and Complex bits, so you have a phase function which is generalised to work over a Num and an arbitrary data type instead; e.g. "class Phase c where phase :: Num a => c a -> a". But what happens if, say, somebody adds a Moon data type, and they want to write a phase function which returns the phase of such a moon? Phases of the moon certainly aren't Nums, nevermind the fact that you probably want to supply your moon phase's function with some sort of date as an extra parameter, which means the Phase type class isn't flexible enough.
Here's the code that does exactly as you wish:
{-# OPTIONS -fglasgow-exts #-}
import qualified Complex
class Phase a b | a -> b where phase:: a -> b
instance (RealFloat a) => Phase (Complex.Complex a) a where phase = Complex.phase
data MoonPhase = P1 | P2 | P3 | P4 deriving Show
instance Phase Int MoonPhase where phase x = if x `mod` 4 == 0 then P1 else P4
instance Phase MoonPhase (Int->Int) where phase P1 x = x phase P2 x = x+1
main = do putStrLn $ show $ phase ( (1.0::Float) Complex.:+ (1.0::Float)) putStrLn $ show $ phase (0::Int) putStrLn $ show $ phase P1 (2::Int)
You can evaluate a phase of a complex number, get a phase of the moon corresponding to some integer, and even convert a phase of the moon to a time (given another integer as a reference time). Whereas the first two functions take one argument, the latter phase takes "two arguments". The class Phase takes the classical first-argument overloading. Other overloading schemes are possible (e.g., the ones that overload based on the result -- something that C++ just can't do: e.g., Read). If we need to evaluate phases of Saturn moons (and we overload on the first argument), we can resolve the overloading using newtype:
newtype SaturnTime a = ST a instance Phase (SaturnTime Int) (Int -> MoonPhase) where phase x moon_index = P1
newtypes add no run-time overhead, and actually help in making the code more explicit. Regarding Koening lookup: as I read in DDJ, it's just a hack! First the committee added the namespaces, and then realized that using operators like << became hugely inconvenient. So Koening came up with a hack. Shouldn't a language be designed in a more systematic way? Speaking of the language design, November 2003 issue of Dr.Dobbs J. has an interesting article: "C++ Compilers and ISO Conformance" [by Brian A. Malloy, James F. Power and Tanton H. Gibbs, pp. 54-60]. Here's a summary. C++ standard has been ratified by the ISO Committee in September 1998. There is no conformance suite however. So, we cannot tell how well a particular compiler complies with a standard. The authors of an article decided to create an approximate conformance suite -- from the examples given in the standard itself. It's a hard job -- the examples aren't meant to be a compiled code, so some declarations and other pieces have to be filled in. The result cannot be considered a truly compliance suite because not all features of the language are illustrated in examples, and the distribution of the examples is uneven. Nevertheless, it's a start. The authors of the article have tested several compilers. The bottom line -- after five years, no single compiler fully complies with the standard. The best compiler, from the Edison Group (a three-person company) fails only 2 tests. Intel's compiler fails three. Visual C++ 7.1 from Microsoft fails 12. Gcc 3.3 fails 26. The latter number shows that a wide community participation and OpenSource do not necessarily lead to a better product. Gcc 3.3 is also one of the slowest compilers. But there is worse news for C++. C++ Language Standard consists of 776 pages, describing C++ language and the C++ core library. At present, 411 points in the C++ language part and 402 points in the library part have been identified as questionable or outright erroneous. 93 language issues have been already acknowledged as errors. That is, EVERY page of the standard, on average, contains some issue! The committee obviously didn't bother to check their examples. Well, even now there isn't a compiler that complies with the standard -- whatever the compliance may mean. Not only programmers don't know what some C++ rules mean. Not only compiler writers are puzzled. Even the committee itself obviously doesn't know how _many_ features are supposed to work. Can you imagine more shoddy work? Incidentally, here's one questionable example from the standard (which has been acknowledged as an error in the Technical Corrigendum 1). typedef int f; struct A { friend void f(A &); operator int (); void g(A a) { f(a); } }; f(a) is ambiguous: it could mean an invocation of method A.f. OTH, it may mean casting 'a' to an integer: f(a) may read int(a) (if typedef takes effect), which is a cast. Both interpretations are valid. The authors of the standard assumed that a compiler could disambiguate. After the compiler writers tried and failed, the committee admitted that perhaps the example shouldn't compile after all.
On 27/02/2004, at 1:13 PM, oleg@pobox.com wrote:
For example, say I'm writing the Data.Complex module; there's a function in that module "phase :: RealFloat a => Complex a -> a". So, how do you put this phase function into a type class? Perhaps you could abstract away from the RealFloat and Complex bits, so you have a phase function which is generalised to work over a Num and an arbitrary data type instead; e.g. "class Phase c where phase :: Num a => c a -> a". But what happens if, say, somebody adds a Moon data type, and they want to write a phase function which returns the phase of such a moon? Phases of the moon certainly aren't Nums, nevermind the fact that you probably want to supply your moon phase's function with some sort of date as an extra parameter, which means the Phase type class isn't flexible enough.
Here's the code that does exactly as you wish:
{-# OPTIONS -fglasgow-exts #-}
import qualified Complex
class Phase a b | a -> b where phase:: a -> b
instance (RealFloat a) => Phase (Complex.Complex a) a where phase = Complex.phase
data MoonPhase = P1 | P2 | P3 | P4 deriving Show
instance Phase Int MoonPhase where phase x = if x `mod` 4 == 0 then P1 else P4
instance Phase MoonPhase (Int->Int) where phase P1 x = x phase P2 x = x+1
main = do putStrLn $ show $ phase ( (1.0::Float) Complex.:+ (1.0::Float)) putStrLn $ show $ phase (0::Int) putStrLn $ show $ phase P1 (2::Int)
Very, very nice Oleg :). I'm glad to know that we can achieve such things using the existing type class mechanisms already. However, this still doesn't solve the problem, because: 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), 2) I then have to declare instances of that type class for every function I define, 3) the type signature for phase reveals no clues about how to use that function. So unfortunately, this is hardly a scalable solution. The entire reason I came up with the idea is because if we use type classes to implement this sort of overloading, we have to know every single possible function that any module author will ever create, and declare classes for those functions in advance. This is fine if you're declaring truly polymorphic functions which are designed from the start to be totally general, but it is not designed for functions which may do vastly different things and may contain totally different type signatures, but share the same name because that would be a sensible thing to do. (e.g. the phase function mentioned above.) With the per-type namespace separation I'm advocating, you do not need to know and declare in advance that each function "will be" overloaded, you simply write a FiniteMap.add function and a Set.add function, and you get a simpler form of namespace separation (or overloading) based on the first parameter that's passed to it. It is a solution which is more _flexible_ than requiring type class definitions, and it is better than having hungarian notation for functions. In fact, I think that, right now, if we replaced the current namespace separation offered by the hierarchical module system, and instead only had this sort of per-type namespace separation, things would still be better! I realise my idea isn't very general in that it only allows this namespace lookup/overloading based on the type of a single argument parameter, and I think it would be possible with a bit more thinking to generalise it to work based on multiple arguments (e.g. via argument-dependent lookup, or whatnot). But even in its current form, I honestly think it offers far more flexibility and would lead to cleaner APIs than is currently possible. -- % Andre Pang : trust.in.love.to.save
On Fri, 27 Feb 2004 ozone@algorithm.com.au wrote:
On 27/02/2004, at 1:13 PM, oleg@pobox.com 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),
2) I then have to declare instances of that type class for every function I define,
3) the type signature for phase reveals no clues about how to use that function.
Declaring a type class instance is really no problem. You just need to write an "instance Class (Type)" instead of "function :: Type" on the line before the function declaration. The type on "phase" itself wouldn't provide much information, but the list of instances in each module defines would be informative. Something like :info wouldn't be much help without modification.
So unfortunately, this is hardly a scalable solution. The entire reason I came up with the idea is because if we use type classes to implement this sort of overloading, we have to know every single possible function that any module author will ever create, and declare classes for those functions in advance. This is fine if you're declaring truly polymorphic functions which are designed from the start to be totally general, but it is not designed for functions which may do vastly different things and may contain totally different type signatures, but share the same name because that would be a sensible thing to do. (e.g. the phase function mentioned above.)
In the paper "Object-Oriented Style Overloading for Haskell", Mark Shields and Simon Peyton-Jones. One of the things they propose is adding method constraints to the type system which (as far as I can tell) basically amounts to generating a type class for each funtion name, and letting you write constraints like (foo :: Int -> Int) on your function. They would set up the type classes like "class Has_foo a where foo :: a", which can causes problems if your argument and return value are polymorphic under a class constraint rather than concrete types. Making the method classes implicitly closed would probably help here. (closed classes are another suggestion). While making that closed world assumption it would probably be nice if it only selected between the versions of the function that were actually in scope at the moment (so these would act kind of like methods that overload if you import several of them, rather than conflicting like normal). As long as we are integrating these special type classes into the language we can make sure things like error messages and ghci give decent information, maybe listing all the different types the function is imported at, and where each version is defined.
With the per-type namespace separation I'm advocating, you do not need to know and declare in advance that each function "will be" overloaded, you simply write a FiniteMap.add function and a Set.add function, and you get a simpler form of namespace separation (or overloading) based on the first parameter that's passed to it. It is a solution which is more _flexible_ than requiring type class definitions, and it is better than having hungarian notation for functions. In fact, I think that, right now, if we replaced the current namespace separation offered by the hierarchical module system, and instead only had this sort of per-type namespace separation, things would still be better!
How much of the structure of the first paramater would you look at? Could you an implementation for pairs that depended on the actual types in the pair? I think you should try to take advantage of the existing type class machinery as much as possible here, even if what you want are not exactly (standard) type classes.
I realise my idea isn't very general in that it only allows this namespace lookup/overloading based on the type of a single argument parameter, and I think it would be possible with a bit more thinking to generalise it to work based on multiple arguments (e.g. via argument-dependent lookup, or whatnot). But even in its current form, I honestly think it offers far more flexibility and would lead to cleaner APIs than is currently possible.
Read the paper and see if you think something like that might be useful. In any case, I think there's a decent chance that something useful for this would also be useful for building interfaces to object-oriented libraries, and vicea versa. I think there's probably something that covers both cases nicely and uniformly. Brandon
-- % Andre Pang : trust.in.love.to.save _______________________________________________ Haskell mailing list Haskell@haskell.org http://www.haskell.org/mailman/listinfo/haskell
On 27/02/2004, at 4:48 PM, Brandon Michael Moore wrote:
On Fri, 27 Feb 2004 ozone@algorithm.com.au wrote:
On 27/02/2004, at 1:13 PM, oleg@pobox.com 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),
2) I then have to declare instances of that type class for every function I define,
3) the type signature for phase reveals no clues about how to use that function.
Declaring a type class instance is really no problem.
I agree that declaring a type class instance per function is not a huge deal (if it can be automatically done by the compiler). 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, it's a function which is When you declare a type class, you are making a trade-off: you are saying that the interface for this function is forever set in stone, and it cannot be changed by any instances under any circumstances. In return for saying that interface is immutable, you get two major benefits: (1) an immutable interface, i.e. so you can guarantee that whenever you use ==, you _know_ the type signature is :: Eq a => a -> a -> Bool, and no instance can try to subvert that (unless your name is Oleg ;), and (2) you get very powerful overloading capabilities. However, the disadvantage of this tradeoff is that because the type signature is now set, you just used up another function name in the namespace. So type classes are the wrong approach to solve this problem, because what I'm after is being able to clutter up a namespace as much as I like with whatever names I like, but I don't want a polymorphic function--I want a function which only operates on one specific, primary data type.
With the per-type namespace separation I'm advocating, you do not need to know and declare in advance that each function "will be" overloaded, you simply write a FiniteMap.add function and a Set.add function, and you get a simpler form of namespace separation (or overloading) based on the first parameter that's passed to it. It is a solution which is more _flexible_ than requiring type class definitions, and it is better than having hungarian notation for functions. In fact, I think that, right now, if we replaced the current namespace separation offered by the hierarchical module system, and instead only had this sort of per-type namespace separation, things would still be better!
How much of the structure of the first paramater would you look at? Could you an implementation for pairs that depended on the actual types in the pair? I think you should try to take advantage of the existing type class machinery as much as possible here, even if what you want are not exactly (standard) type classes.
The idea is if you write "fm.add", you look at the type of fm as much as possible. If you see that fm is polymorphic, all bets are off, and the compiler raises an error and quits with prejudice. If fm is monomorphic, you should be able to infer its type (which includes pairs/tuples) and thus know which namespace to select to find the correct add function. So the main requirement for this to work is whether it's possible to infer the type of fm; since I'm not a type theorist, I have no idea if that is in fact possible at all.
I realise my idea isn't very general in that it only allows this namespace lookup/overloading based on the type of a single argument parameter, and I think it would be possible with a bit more thinking to generalise it to work based on multiple arguments (e.g. via argument-dependent lookup, or whatnot). But even in its current form, I honestly think it offers far more flexibility and would lead to cleaner APIs than is currently possible.
Read the paper and see if you think something like that might be useful. In any case, I think there's a decent chance that something useful for this would also be useful for building interfaces to object-oriented libraries, and vicea versa. I think there's probably something that covers both cases nicely and uniformly.
I've had a read of both the SPJ/Shields paper on OO-style overloading in Haskell, and I've also had a skim over another paper called "A Second Look at Overloading" which describes another overloading calculus called System O. I don't think either paper directly addresses the problem I'm trying to solve, although some elements in the paper (e.g. closed classes) may provide a framework which is capable of addressing the problem, if something like fm.add can be translated to such a framework via major syntactic sugar :). -- % Andre Pang : trust.in.love.to.save
ozone@algorithm.com.au wrote:
I've had an idea stewing in my head to do with per-type function namespaces, .....
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 the Set type, that will only get called if the 'x' in "x.add" is of type :: Set. So, like OO languages which inherently give separate namespaces to their different objects, here we give separate namespaces to different (monomorphic) types. In this case, if one simply writes "add" instead of "x.add", the compiler throws an error, because there is no 'add' function defined in the default namespace; add is only defined when a programmer writes "x.add" where x :: FiniteMap or x :: Set[1].
Wouldn't something like Koenig Lookup in C++ solve this problem as well, without the need for a new (strange) syntax? That is, a function is looked up in the namespaces of its arguments as well as in the normal places. So "add fm k v" where fm :: FiniteMap, x :: Int, v :: String would look for "add" in the modules where FiniteMap, Int and String was defined. However, both of these methods means that we have to know the types of the arguments to be able to resolve names, probably forcing us to write a lot more type signatures than otherwise. It feels a bit like we're just trading one problem for another.. ("lots of qualified names" vs. "lots of type signatures"). But it might be worth it.. Would Koenig lookup (or something similar) be feasable in Haskell? Or would it just lead to unnecessary complexity? Name lookup in C++ is not exactly simple, so it might be a bad place to borrow ideas from.. ;) /Peter
Peter Strand <peter@zarquon.se> writes:
That is, a function is looked up in the namespaces of its arguments as well as in the normal places. So "add fm k v" where fm :: FiniteMap, x :: Int, v :: String would look for "add" in the modules where FiniteMap, Int and String was defined.
I suppose partial application would complicate things a bit here -- you don't necessarily know what the arguments are. -kzm -- If I haven't seen further, it is by standing in the footprints of giants
I think that this is a problem that can be solved with a simple convention change, rather than a language extension - instead of appending type names, I think it would be much better if modules simply used the short, convenient, common names and expected the user to import them qualified where overlap is a problem - in short, do exactly what DData does. It's slightly more verbose than OO-style: "Map.add map key value" instead of "map.add(key, value);" but I don't think that "what OO does" is a good language design target. Another random thought: what you describe sounds awfully similar to typeclasses, just with a single function in each typeclass. Abe ozone@algorithm.com.au writes:
I've had an idea stewing in my head to do with per-type function namespaces, that the current module namespace discussion reminded me about. The problem is that there is a limited namespace for functions, so that if you define a new data type, it is unwise to call functions which work on that data type a very generic name such as 'add'. An example of this is Data.FiniteMap and Data.Set: both data types define a function to add things to their respective data types.
addToFM :: Ord key => FiniteMap key elt -> key -> elt -> FiniteMap key elt addToSet :: Ord a => Set a -> a -> Set a
So at the moment, many Haskellers will append the type name to the function to indicate that it only works on that particular data type. In this respect, Haskell is at a disadvantage vs most object-oriented languages, because in them, you can write "x.add", and the type system will perform "object-oriented polymorphism" for you and call the correct add method, no matter if x is a FiniteMap or a Set. Writing "addToFM fm ..." or "addToSet set ..." is surely a lot more inconvenient than writing "fm.add" or "set.add", no?
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 the Set type, that will only get called if the 'x' in "x.add" is of type :: Set. So, like OO languages which inherently give separate namespaces to their different objects, here we give separate namespaces to different (monomorphic) types. In this case, if one simply writes "add" instead of "x.add", the compiler throws an error, because there is no 'add' function defined in the default namespace; add is only defined when a programmer writes "x.add" where x :: FiniteMap or x :: Set[1].
There are a number of means by which the x in x.add can be communicated to the actual function: it's similar to the hidden 'self' or 'this' variable that's present when you invoke a method on an object in OO. Perhaps x is passed to the function as its first parameter, or maybe it could be its last parameter, or even an arbitrary parameter (where the parameter it's passed as could be defined in the type signature of the function). Perhaps 'self' or 'this' could be an implicit parameter. Any one of them will work just fine, I think.
However, this scheme is only for functions which have such a 'primary' data type to be associated with, such as FiniteMap or Set. For functions which are truly polymorphic (such as ==), you still leave them in the default namespace. Perhaps it's sensible to even make it a requirement that functions in the default namespace must be polymorphic: if they are monomorphic, they are associated with operating on a specific data type, so they should belong in a type-specific namespace. You then still guarantee that such commonly-used polymorphic functions cannot be 'hijacked' to have stupid type signatures; i.e. == is always guaranteed to be :: Eq a -> a -> Bool.
Anyhow, feedback is more than welcome; I would certainly welcome this addition if it's feasible. It feels inferior to be typing in 'addToFM foo' all the time when our OO brethren type the simpler and more succinct 'foo.add', especially given that Haskell's type system is far more powerful!
1. I haven't thought hard enough about whether it would be possible to have the same function name in both the 'default' namespace as well as in per-type namespaces, but my gut feeling says it should be OK.
-- % Andre Pang : trust.in.love.to.save _______________________________________________ Haskell mailing list Haskell@haskell.org http://www.haskell.org/mailman/listinfo/haskell
On 27/02/2004, at 8:28 AM, Abraham Egnor wrote:
I think that this is a problem that can be solved with a simple convention change, rather than a language extension - instead of appending type names, I think it would be much better if modules simply used the short, convenient, common names and expected the user to import them qualified where overlap is a problem - in short, do exactly what DData does. It's slightly more verbose than OO-style: "Map.add map key value" instead of "map.add(key, value);" but I don't think that "what OO does" is a good language design target.
This is exactly what was discussed in the thread before I barged in with per-type function namespaces, and it's not a good solution because of what Alastair has mentioned. It's also not a good solution because I still have to type "Map.add map" instead of "map.add": the type system already knows that map of type Map, so why should I have to qualify it even more by sticking a module name in front, and also encode the type name into my function because the module/namespace system isn't good enough to deal with this issue? I also agree that "what OO does" is not a good language design target, but I do think that "leverage type system to make programming nicer for you" is a good design target :). We're using a form of hungarian notation for function names, which is necessary because of a global namespace; OO people abolished this a long time ago.
Another random thought: what you describe sounds awfully similar to typeclasses, just with a single function in each typeclass.
It's not the same as a single-function type class, for the reasons that I pointed out to Keith Wansbrough in an earlier email. -- % Andre Pang : trust.in.love.to.save
Alastair Reid <alastair@reid-consulting-uk.ltd.uk> wrote:
Haskell's module system provides a way for a module to merge multiple modules into one but provides no way to eliminate any ambiguities this may create. If we want to be able to use names like 'create' instead of 'createFont', we need to change the module system. The obvious fix would have some of the flavour of the ML module system where a module can export a structured list of names instead of exporting a flat list of names.
i'm not familiar with ml's module system so i don't know if my suggestion is the same or similar, but could a good solution be to allow modules to be exported as qualified modules? for example module Graphics ( module Font qualified, module Color qualified, module Window qualified, ... ) where ... chris moline
participants (19)
-
Abraham Egnor -
ajb@spamcop.net -
Alastair Reid -
Andre Pang -
Brandon Michael Moore -
Chris Moline -
Christian Maeder -
David Bergman -
Gabriel Dos Reis -
Keith Wansbrough -
Ketil Malde -
Koen Claessen -
Lennart Augustsson -
Malcolm Wallace -
oleg@pobox.com -
ozone@algorithm.com.au -
Peter Strand -
Sven Panne -
Wolfgang Jeltsch