
Wolfgang Jeltsch
Personally, I would greatly prefer to have libraries that do not clash with common Prelude functions.
I would solve this problem by reducing the Prelude to just a core. List function could go, for example, (mostly) into Data.List.
If this means that you must import Data.List almost everywhere, this won't change anything - only add yet another import to every file. I know most - if not all - of my modules use lists, but just in case, I checked with the darcs sources - 95 of 107 source files appear to use lists. Ignoring the : and ++ operators (looking for null, map, and filter) the count is 76 files.
If you always have to qualify it, what's the advantage of Data.Set.empty over emptySet again? At least with 'emptySet' I know what to grep for.
I would import Data.Set as Set. So I would use Set.empty instead of emptySet.
The problem is that I might import Data.Set as S. Joe would import it as something else, and Frank would use yet another prefix.
The advantage is that Set.empty is more structured.
I'm not sure why that is such an advantage. To me, structure sounds like a means rather than an end. If it made things simpler, I'd be all for it, but IMO it doesn't - and it makes some things more difficult.
You can easily distinguish the kind of operation (empty) and the type you are working with (Set).
Only by convention - i.e. I must manually rename the module in a way that is meaningful with respect to the type. (As an aside, 'empty' is a rather poor example, as it is not a Prelude function. Take 'null' or 'map' or - why not - '(:)' instead) Don't get me wrong, I don't want to go back to the old ways of prefixing *every* identifier with a module abbreviation. But I resent having to add a multi-line half-standardized set of imports to over 90% of my source files. In short, I'm fairly comfortable with the scope of the Prelude¹ and I don't mind modules like e.g. Data.Map and Data.Set, which are similar in many ways to recycle some of their identifiers. However, I think minimizing the number of name clashes should be a goal, and modules that are intended to be used together, should try to avoid using the same identifiers. And this implies that Prelude identifiers should *not* be recycled, unless you intend to replace the Prelude. -k ¹ I might want to shove some of the association list stuff into Data.List, but certainly not all list functionality. -- If I haven't seen further, it is by standing in the footprints of giants

Ketil Malde
I would solve this problem by reducing the Prelude to just a core. List function could go, for example, (mostly) into Data.List.
If this means that you must import Data.List almost everywhere, this won't change anything - only add yet another import to every file.
People often overlook the useful facility of the module system to group multiple common imports into a single re-exporting super-module. With my proposal, you would simply replace the implicit "import Prelude" with an explicit "import Prelude.Standard" (or use a build flag like -fimplicit-prelude, or +98). The idea is that backward compatibility with the old Prelude is preserved as much as possible, but the experimental ability to replace or remove parts of the Prelude is gained. Over time and through experience, a new consensus about what should be included might emerge.
I know most - if not all - of my modules use lists, but just in case, I checked with the darcs sources - 95 of 107 source files appear to use lists. Ignoring the : and ++ operators (looking for null, map, and filter) the count is 76 files.
Some data-structures people (e.g. Chris Okasaki) are of the opinion that lists tend to be over-used (because they are built-in), when other datatypes might be much more appropriate. Regards, Malcolm

Hello Malcolm, Thursday, February 23, 2006, 1:58:36 PM, you wrote: MW> People often overlook the useful facility of the module system to group MW> multiple common imports into a single re-exporting super-module. With MW> my proposal, you would simply replace the implicit "import Prelude" with MW> an explicit "import Prelude.Standard" (or use a build flag like MW> -fimplicit-prelude, or +98). MW> The idea is that backward compatibility with the old Prelude is MW> preserved as much as possible, but the experimental ability to replace MW> or remove parts of the Prelude is gained. import Prelude ($) can't solve this problem? -- Best regards, Bulat mailto:Bulat.Ziganshin@gmail.com

Bulat Ziganshin
MW> With my proposal, you would simply replace the MW> implicit "import Prelude" with an explicit "import MW> Prelude.Standard"
import Prelude ($) can't solve this problem?
One of the problems with the current mechanism for overriding Prelude definitions, is that every module that /uses/ such an entity must also explicitly hide the original Prelude: module NewMap (map) where import Prelude () map = ... module User where import Prelude hiding (map) import NewMap By forcing the H' Prelude to be explicit by default, it removes this nuisance. Instead, you just change which Prelude you import. module Prelude.NewMap (module Prelude.Haskell98, map) where import Prelude.Haskell98 hiding (map) map = ... module User where import Prelude.NewMap Note that, my suggestion is that compilers which continue to support the Haskell'98 language will continue to give you the original implicit Prelude in that mode. It is only Haskell-prime programs that would be affected. Yes, many trivial programs would acquire one extra import decl - is that such a big deal? Especially for beginners, I'm thinking that some teachers might /prefer/ to remove lots of the current Prelude, and build up students' knowledge gradually, by allowing them to write map, fold, curry, etc for themselves, without name clashes. Later, they could "switch on" more of the language, like numeric classes, by need. Regards, Malcolm

Malcolm Wallace wrote:
Yes, many trivial programs would acquire one extra import decl - is that such a big deal?
No, but overall it feels like a slight loss, because I'd expect the modules that gained a line to be shorter on average, and adding a line to a shorter module seems like a bigger deal than deleting a line from a longer module. The impact would be especially noticeable on one-liners like main = putStr (s++show s) where s="main = putStr (s++show s) where s=" In fact, this suggests a compromise: how about implicitly importing the Prelude only if the module header is omitted? That way there'll be no impact on short (single-module) programs. -- Ben

On 2/23/06, Ben Rudiak-Gould
In fact, this suggests a compromise: how about implicitly importing the Prelude only if the module header is omitted? That way there'll be no impact on short (single-module) programs.
+1
--
Taral

Malcolm Wallace
One of the problems with the current mechanism for overriding Prelude definitions, is that every module that /uses/ such an entity must also explicitly hide the original Prelude:
I guess I don't quite understand what you are trying to achieve. Case one: existing project where you want to replace the Prelude Subcase a: the project already has a dummy module MyPrelude which just reexports the Prelude, and which is imported everywhere. - Great, all you need to do is modify MyPrelude! - Benefit from your proposal: you could skip import Prelude () from all .hs files (or -fno-implicit-prelude from your Makefile) - Drawback: anybody actually *do* this in real code without concrete plans for Prelude replacement? Subcase b: you don't have a MyPrelude. - Too bad, you need to modify all your .hs files *anyway* (Unless the good and benevolent secret keepers of the compiler code would implement a -fprelude-is MyPrelude switch) Subcase c: you want an alternative Prelude only in some of your files. Well, you are going to need to modify those files. At least you get to leave the rest alone. Case two: a new project with a different Prelude Well, if you're starting a new project, you can of course choose your own imports as you wish. Currently, the cost in one extra line in each file or the -fno-implicit option. I don't think saving one line from each file in projects using alternative Preludes justify adding one line to each file in projects using the standard Prelude. If there is any reason to think custom Preludes will outnumber the standard one, I'll change my mind. This sounds like yet another feature that is greatly interesting and wonderful to language hackers, and mostly annoying to the average programmer - partly by requiring extra overhead and boilerplate stuff, and partly by breaking old code. To summarize: * I don't think this is a good solution to name clashes; to use e.g. Set (and, hey, I agree with Okasaki and you that it should probably be used more instead of lists) I would need to use fine-grained imports management which adds administrative overhead. (A simple rename of a couple of offending identifiers would solve the problem. Why not 'isEmpty' instead of 'null', for instance?) * The standard Prelude is IMO going to be vastly more common than custom replacements. Custom Prelude users are also likely to be more accomplished Haskell programmers. Any administrative burden should be on the custom Prelude users. * That said, I'm all in favor of a switch to tell the compiler to use a custom module instead of the implicit Prelude. Or perhaps more generally: provide an implicit, project-wide import? Something like: ghc --make -fno-implicit-prelude -fimplicit-import MyPrelude Main.hs * Much of my resistance would go away if I had better tools to manage module interfaces more automatically. As it is now, import/export administration is already enough of a chore to be noticeable. -k -- If I haven't seen further, it is by standing in the footprints of giants

Ketil Malde wrote:
(A simple rename of a couple of offending identifiers would solve the problem. Why not 'isEmpty' instead of 'null', for instance?)
because both denote the same concept. -- -- Johannes Waldmann -- Tel/Fax (0341) 3076 6479/80 -- ---- http://www.imn.htwk-leipzig.de/~waldmann/ -------

Ketil Malde
One of the problems with the current mechanism for overriding Prelude definitions, is that every module that /uses/ such an entity must also explicitly hide the original Prelude:
I guess I don't quite understand what you are trying to achieve.
The problem, as I see it, is that the Prelude steals too many good names from the user. For instance, there are complaints about the Numeric class hierarchy being inconvenient. So if you want to re-define arithmetic using groups, rings, and whatnot, then you must really use new names for (+), (*), etc, or force every user module to hide the Prelude. Neither alternative is terribly attractive.
* The standard Prelude is IMO going to be vastly more common than custom replacements. Custom Prelude users are also likely to be more accomplished Haskell programmers. Any administrative burden should be on the custom Prelude users.
A valid point. At the moment however, I think the burden on custom users is too high.
This sounds like yet another feature that is greatly interesting and wonderful to language hackers, and mostly annoying to the average programmer - partly by requiring extra overhead and boilerplate stuff, and partly by breaking old code.
There may well be other (small but) breaking changes in Haskell-prime. We shouldn't break things just for the sake of it, but this is an attempt to make the language smaller rather than to extend it.
* That said, I'm all in favor of a switch to tell the compiler to use a custom module instead of the implicit Prelude. Or perhaps more generally: provide an implicit, project-wide import? Something like:
ghc --make -fno-implicit-prelude -fimplicit-import MyPrelude Main.hs
Yes, there may be other ways to achieve the same goal.
* Much of my resistance would go away if I had better tools to manage module interfaces more automatically. As it is now, import/export administration is already enough of a chore to be noticeable.
Agreed. Regards, Malcolm

On Fri, Feb 24, 2006 at 09:20:10AM +0100, Ketil Malde wrote:
I don't think saving one line from each file in projects using alternative Preludes justify adding one line to each file in projects using the standard Prelude. If there is any reason to think custom Preludes will outnumber the standard one, I'll change my mind.
I very much agree. I can see the theoretical appeal of a minimal prelude, but I don't think it outweighs the practical and real advantages of a somewhat inclusive one. especially when the module system already makes it easy to hide names on import. Not that I wouldn't mind reevaluating what should be in the prelude, but I don't see the need for pushing towards a truely minimal one.
This sounds like yet another feature that is greatly interesting and wonderful to language hackers, and mostly annoying to the average programmer - partly by requiring extra overhead and boilerplate stuff, and partly by breaking old code.
Hey, I am a language hacker and would find it anoying :) I do find it quite bothersome that modules reexport things the prelude does, like List for instance rexporting (and,or,...) Things in the prelude are already in scope normally, if another module reexports them then it just anoys people that want to override them as they have to add hiding clauses to multiple imports. I would be very happy if we had a policy of making the exports from the standard libraries disjoint. In general, I don't think reexporting names from other modules is good style unless there is a very good reason. But I know others disagree with me on this, or at least have a different point at which they decide a reason is good enough :) John -- John Meacham - ⑆repetae.net⑆john⑈

Malcolm Wallace
you would simply replace the implicit "import Prelude" with an explicit "import Prelude.Standard" (or use a build flag like -fimplicit-prelude, or +98).
Why not keep it implicit? If you want the alternative behavior, you know where to get it (import Prelude (), -fno-implicit-prelude, etc). This way, you avoid breaking a lot of existing code, and also a lot of "hello world"-class programs could be written without any explicit imports or special tricks. Any programs written to use a more granular set of Prelude modules, would likely need an array of imports anyway, so the loss would be less in that case.
Some data-structures people (e.g. Chris Okasaki) are of the opinion that lists tend to be over-used (because they are built-in), when other datatypes might be much more appropriate.
Hmm...so the best way to educate programmers about this is to make lists harder to use? :-) Anyway, I'll see your Okasaki, and raise you one Simon Thomson (since "The Craft.." is the only Haskell book I have on my desk at the moment). Lists aren't mentioned until chapter 4, but chapter 4 and 5 discuss lists, chapter 5 is called "Generalization", but as far as I can tell, still talks exclusively about lists, and in the remaining chapters, there are lists on nearly every page. Over-used, perhaps, but the fact is that lists are - like it or not - a staple of FP in general, and Haskell in particular. Now, I'm sure a lot of uses for List could be replaced by more specific and appropriate types: Stack, LazyStream, IterationMonad, Deque, FingerTree etc etc. But this adds complexity to your program - if a list does the job well enough, why not use it? I'm reminded of one of the XP idioms: always first try to solve the problem in the simplest way that can possibly work. </hubris> :-) -k -- If I haven't seen further, it is by standing in the footprints of giants

On Thu, Feb 23, 2006 at 10:58:36AM +0000, Malcolm Wallace wrote:
Some data-structures people (e.g. Chris Okasaki) are of the opinion that lists tend to be over-used (because they are built-in), when other datatypes might be much more appropriate.
While this may be true of lesser languages and compilers, for a whole lot of stuff lists end up faster and more efficient than other data structures. I know when data.set and data.map first appeared I started converting things to use them and actually hurt performance in some cases. unless you can amortize the cost of building a map/set over many lookups with good coverage and a fair number of values then they tend to not be worth it. I think there are a couple reasons for this: 1. amortized bounds on cost only start mattering when your number of elements gets big, most uses of data structures are for a small number of elements. 2. lazyness, when doing an 'elem' on a list, it only needs to evaluate as much of the list as is needed to find the element. if the list is built with a 'build' then the list never even needs to exist in memory. complicated data structures often need to examine all valuse to build their tree properly. 3. list lookups are O(n). building a order based structures is O(n*log(n)). this means if you are doing less than log(n)* lookups then a list is more efficient, perhaps signifigantly so compared to building a set when combined with the other points. I am guilty of forgetting this on occasion myself. 4. ghc's list deforestation is friggen awesome. lists are not so much a data structure in haskell as a programming construct used to express compositions of routines that work on sequences of items. This alone makes them quite special and more useful than 'data structures'. incidntally, I don't think we have build/foldr rules for Set.from/toList and Map.from/toList. I think they can help the performance of Set/Map signifigantly. * actually n*log(n)/(n - log(n)) which means lists are even faster for small numbers of lookups. John -- John Meacham - ⑆repetae.net⑆john⑈

John Meacham wrote:
On Thu, Feb 23, 2006 at 10:58:36AM +0000, Malcolm Wallace wrote:
Some data-structures people (e.g. Chris Okasaki) are of the opinion that lists tend to be over-used (because they are built-in), when other datatypes might be much more appropriate.
While this may be true of lesser languages and compilers, for a whole lot of stuff lists end up faster and more efficient than other data structures.
I think the issue here is not to decide between lists or trees, but to write programs in such a way that this decision is not made too early and can be easily changed later: it is between using a concrete collection data type and an abstract one (a.k.a. type class). We had this discussion before. respectfully submitted, -- -- Johannes Waldmann -- Tel/Fax (0341) 3076 6479/80 -- ---- http://www.imn.htwk-leipzig.de/~waldmann/ -------

On 23/02/06, John Meacham
On Thu, Feb 23, 2006 at 10:58:36AM +0000, Malcolm Wallace wrote:
Some data-structures people (e.g. Chris Okasaki) are of the opinion that lists tend to be over-used (because they are built-in), when other datatypes might be much more appropriate.
While this may be true of lesser languages and compilers, for a whole lot of stuff lists end up faster and more efficient than other data structures. I know when data.set and data.map first appeared I started converting things to use them and actually hurt performance in some cases. unless you can amortize the cost of building a map/set over many lookups with good coverage and a fair number of values then they tend to not be worth it.
I think there are a couple reasons for this: 4. ghc's list deforestation is friggen awesome. lists are not so much a data structure in haskell as a programming construct used to express compositions of routines that work on sequences of items. This alone makes them quite special and more useful than 'data structures'.
Exactly. Even without deforestation, in a lazy language, lists are essentially one's loops, or at least, they largely take the place of what one would use loops for in imperative languages. In some sense, lists are the 'simplest' datastructure required to express loops. This is exactly why lists are so common. If people ask why lists are so common in lazy functional languages, they ought also to ask why loops are so common in imperative languages. Such datastructures which are 'universal' in this sense for expressing particular ideas tend to be popular. The Maybe type constructor is so popular because it expresses partiality and nothing more. - Cale

Cale Gibbard wrote:
This is exactly why lists are so common. If people ask why lists are so common in lazy functional languages, they ought also to ask why loops are so common in imperative languages.
A loop is a sequence of actions. In FP, I don't typically have actions, rather I collect values, so I'd need several collections types, among them: sequences, and among them: classical left-biased lists. I admit that my typical Haskell program does use such lists all over the place, but mostly in monad comprehensions: do x <- ... ; guard ; let ... ; return ... and often it's a list only because there is no such syntactical convenience for, e. g. Sets. (I have to write lots of setToList/mkSet, which look plain ugly.) Even if I really want sequences, I rarely rely on the left-biased representation. (E. g. in the above example, there's no visible pattern matching on the 'cons'.) For reference, in Java, List<> is an interface, not a type (it has instances like LinkedList<> and ArrayList<>). And, there's nice syntactic sugar for looping over collections: Collection<E> c; for (E item : c) { ... } (it hides the pre-1.5 Iterator.hasNext/next stuff. I'd say this is an example of moving away from a left-biased representation, or at least freeing the programmer from having to think about it). Respectfully submitted, -- -- Johannes Waldmann -- Tel/Fax (0341) 3076 6479/80 -- ---- http://www.imn.htwk-leipzig.de/~waldmann/ -------

Johannes Waldmann
For reference, in Java, ... there's nice syntactic sugar for looping over collections: Collection<E> c; for (E item : c) { ... } I'd say this is an example of moving away from a left-biased representation, or at least freeing the programmer from having to think about it).
In Haskell, this is called 'fmap'. :-) Regards, Malcolm

Malcolm Wallace wrote:
Johannes Waldmann
wrote:
For reference, in Java, ... there's nice syntactic sugar for looping over collections: Collection<E> c; for (E item : c) { ... }
In Haskell, this is called 'fmap'. :-)
OK, then show me an "instance Functor Set" so that I can use it :-) -- -- Johannes Waldmann -- Tel/Fax (0341) 3076 6479/80 -- ---- http://www.imn.htwk-leipzig.de/~waldmann/ -------

Johannes Waldmann
In Haskell, this is called 'fmap'. :-)
OK, then show me an "instance Functor Set" so that I can use it :-)
instance Function Set where fmap = Data.Set.mapMonotonic Ok, so this introduces a precondition on the function being mapped, so there is a proof obligation on the programmer. But if contexts-on-datatypes worked correctly, data Set a = Ord a => .... then even the "real" map from Data.Set: map :: (Ord a, Ord b) => (a -> b) -> Set a -> Set b could be an instance method of Functor. (Because the Ord constraints would be packaged inside the Set type, rather than needing to be explicit.) Regards, Malcolm

Malcolm Wallace wrote:
But if contexts-on-datatypes worked correctly,
data Set a = Ord a => ....
then even the "real" map from Data.Set:
map :: (Ord a, Ord b) => (a -> b) -> Set a -> Set b
could be an instance method of Functor.
I'd love that. But I don't quite understand: do you think this is/should be possible with: current Haskell? Haskell-Prime? Current ghc (what extensions)? -- -- Johannes Waldmann -- Tel/Fax (0341) 3076 6479/80 -- ---- http://www.imn.htwk-leipzig.de/~waldmann/ -------

But if contexts-on-datatypes worked correctly,
data Set a = Ord a => ....
then even the "real" map from Data.Set:
map :: (Ord a, Ord b) => (a -> b) -> Set a -> Set b
could be an instance method of Functor.
I'd love that. But I don't quite understand: do you think this is/should be possible with: current Haskell? Haskell-Prime? Current ghc (what extensions)?
It is not possible currently, because of the H'98 language definition. I do think it would be nice to fix this in Haskell-prime. However, although the idea is somewhat related to Polymorphic Components http://hackage.haskell.org/trac/haskell-prime/wiki/PolymorphicComponents there is no specific proposal about this issue on the wiki. (It was mentioned on some mailing list in the last couple of months, but I can't find the thread now.) By "working correctly" I mean that: it is a wart in Haskell'98 that you can declare a datatype to require some class constraints on contained elements, but that these extra constraints do not really buy you any expressive power. They just force you to repeat the same context decl on every function that uses such a type. Ideally, the data decl should be more like an "alias", capturing the constraints as part of the semantics associated with the type, so that you don't need to mention the constraints at every usage location of the type. Of course, there are some details to work out, about where you can validly omit the constraints, and where they are still required. Regards, Malcolm

On 2/28/06, Johannes Waldmann
Malcolm Wallace wrote:
But if contexts-on-datatypes worked correctly,
data Set a = Ord a => ....
then even the "real" map from Data.Set:
map :: (Ord a, Ord b) => (a -> b) -> Set a -> Set b
could be an instance method of Functor.
I'd love that. But I don't quite understand: do you think this is/should be possible with: current Haskell? Haskell-Prime? Current ghc (what extensions)?
as Oleg: {-# OPTIONS -fglasgow-exts #-} {-# OPTIONS -fallow-undecidable-instances #-} module Map where import qualified Data.Set class MyMap f a b where myMap :: (a -> b) -> f a -> f b instance (Functor f) => MyMap f a b where myMap = fmap instance (Ord a, Ord b) => MyMap Data.Set.Set a b where myMap = Data.Set.map Jim

Jim Apple wrote:
class MyMap f a b where myMap :: (a -> b) -> f a -> f b instance (Functor f) => MyMap f a b where myMap = fmap instance (Ord a, Ord b) => MyMap Data.Set.Set a b where myMap = Data.Set.map
OK (I guess). But my point was that I want to use "do notation" for Sets (in fact, for any kind of collection) so I'd need the original Functor and Monad. I couldn't use ghc's Rebindable Syntax feature because the types for (>>=) would not match? http://www.haskell.org/ghc/docs/6.4/html/users_guide/syntax-extns.html#rebin... Best regards, -- -- Johannes Waldmann -- Tel/Fax (0341) 3076 6479/80 -- ---- http://www.imn.htwk-leipzig.de/~waldmann/ -------

On 3/1/06, Johannes Waldmann
But my point was that I want to use "do notation" for Sets (in fact, for any kind of collection) so I'd need the original Functor and Monad.
I understand this for Monad. Why not just redefine Functor, Oleg-style?
I couldn't use ghc's Rebindable Syntax feature because the types for (>>=) would not match? http://www.haskell.org/ghc/docs/6.4/html/users_guide/syntax-extns.html#rebin...
Good news, everyone! http://www.haskell.org/ghc/dist/current/docs/users_guide/syntax-extns.html#r... That looks good to me! Jim

Malcolm Wallace
But if contexts-on-datatypes worked correctly,
data Set a = Ord a => ....
then even the "real" map from Data.Set:
map :: (Ord a, Ord b) => (a -> b) -> Set a -> Set b
could be an instance method of Functor.
fmap ($0) . fmap const :: Functor f => f a -> f a When applied to Set Int, how would it represent the intermediate set of functions? Or if it was disallowed, on what basis? -- __("< Marcin Kowalczyk \__/ qrczak@knm.org.pl ^^ http://qrnik.knm.org.pl/~qrczak/

Marcin 'Qrczak' Kowalczyk
But if contexts-on-datatypes worked correctly, data Set a = Ord a => .... then even the "real" map from Data.Set: map :: (Ord a, Ord b) => (a -> b) -> Set a -> Set b could be an instance method of Functor.
fmap ($0) . fmap const :: Functor f => f a -> f a
When applied to Set Int, how would it represent the intermediate set of functions? Or if it was disallowed, on what basis?
Clever example. The intermediate set is :: Set (Int->b->Int), which does not satisfy the construction constraint (Ord a) => Set a. But persuading the type system to reject this is tricky I agree. I'm not a type hacker, so I don't know how to go about it. I suppose if the complete set of class instances for the entire program were known, then you could have a negation predicate, asserting that the intermediate type (Int->b->Int) does not have an instance of Ord, and somehow record this in the type environment fmap ($0) . fmap const :: (Functor f, not Ord a) => f a -> f a But then, there are a whole host of classes that (->) is not an instance of, so this is pretty useless because the constraints will grow enormously for little benefit. In short, I suppose the reason contexts-on-datatypes are as they are currently, is because no-one yet knows how to solve your example. Regards, Malcolm

On Friday 03 March 2006 10:52, Malcolm Wallace wrote:
Marcin 'Qrczak' Kowalczyk
writes: But if contexts-on-datatypes worked correctly, data Set a = Ord a => .... then even the "real" map from Data.Set: map :: (Ord a, Ord b) => (a -> b) -> Set a -> Set b could be an instance method of Functor.
fmap ($0) . fmap const :: Functor f => f a -> f a
When applied to Set Int, how would it represent the intermediate set of functions? Or if it was disallowed, on what basis?
Clever example. The intermediate set is :: Set (Int->b->Int), which does not satisfy the construction constraint (Ord a) => Set a. But persuading the type system to reject this is tricky I agree. I'm not a type hacker, so I don't know how to go about it. I suppose if the complete set of class instances for the entire program were known, then you could have a negation predicate, asserting that the intermediate type (Int->b->Int) does not have an instance of Ord, and somehow record this in the type environment
fmap ($0) . fmap const :: (Functor f, not Ord a) => f a -> f a
But then, there are a whole host of classes that (->) is not an instance of, so this is pretty useless because the constraints will grow enormously for little benefit.
In short, I suppose the reason contexts-on-datatypes are as they are currently, is because no-one yet knows how to solve your example.
Well, that is what wft constraints are for, see http://www.cs.chalmers.se/~rjmh/Papers/restricted-datatypes.ps where the above problem is discussed and a solution proposed. (hasn't this been discussed here, lately?) Ben

On 28/02/06, Johannes Waldmann
Cale Gibbard wrote:
This is exactly why lists are so common. If people ask why lists are so common in lazy functional languages, they ought also to ask why loops are so common in imperative languages.
A loop is a sequence of actions. In FP, I don't typically have actions, rather I collect values, so I'd need several collections types, among them: sequences, and among them: classical left-biased lists.
Sure you do, you have pure functions, there are lots of those. Further, you have things like sequence, which turns a list of monadic actions into a monadic action returning a list of results, and mapM, which is just mapping a function to construct those actions over a list before sequencing them. Numerical algorithms involving iteration can get quite a lot of flexibility out of lists, using them to represent the output and intermediate values of the algorithm at each iteration. These lists are lazy of course, so that you can just take as many iterations off as you need, and since it's so nicely explicit, it's comparatively easy to determine where you'd like to chop, and to apply higher-order methods of reducing error. See "Why Functional Programming Matters" for some brilliant examples. http://www.math.chalmers.se/~rjmh/Papers/whyfp.html
I admit that my typical Haskell program does use such lists all over the place, but mostly in monad comprehensions: do x <- ... ; guard ; let ... ; return ... and often it's a list only because there is no such syntactical convenience for, e. g. Sets. (I have to write lots of setToList/mkSet, which look plain ugly.)
Even if I really want sequences, I rarely rely on the left-biased representation. (E. g. in the above example, there's no visible pattern matching on the 'cons'.)
Then perhaps you're missing out :) It sounds like you might be using lists like a strict functional programmer would, but not really relying on the laziness aspect. When you start doing so, it becomes much more apparent why lists are so special, and, for the many of the places where they work, AVL trees, for example, just wouldn't do. (Not that there's anything against AVL trees, it's just that they're not any kind of a substitute for lists in these common lazy functional programming cases.)
For reference, in Java, List<> is an interface, not a type (it has instances like LinkedList<> and ArrayList<>).
And, there's nice syntactic sugar for looping over collections: Collection<E> c; for (E item : c) { ... } (it hides the pre-1.5 Iterator.hasNext/next stuff. I'd say this is an example of moving away from a left-biased representation, or at least freeing the programmer from having to think about it).
None of that is lazy, so it just isn't the same thing at all. The important point is that the elements and structure of the collection are being constructed one at a time as you iterate over it, and they are easily garbage collected as soon as you're done with them. That's what allows you to say "hey, these are actually loops which just haven't happened yet". Only a list can really easily provide that for linear iteration through a collection of values. Certain trees can come close, (with a logarithmic additional space hit) and trees are quite useful for directed searching, but they don't replace the high position that lists have, just as non-linear forms of recursion don't quite replace common linear recursion. (Look at some imperative programs and count the loops and linear recursions compared with the number of general recursive functions which are not linear recursive.) Lists in the list-monad/list-comprehension usage are nearly trees anyway, due to the means by which they are computed, but you can always think of them as simple lists, or even as single nondeterministic values (due to the monadic structure), which is pretty neat. You can prune the tree by guards or the empty list, and branch by using bind. In some sense, you're defining a long reified loop which is strung along the leaves of a nonlinear recursive call tree. You're not forced to look at it that way, though, which makes things easy to think about when you're actually programming. It's also what makes the list monad (and isomorphic nondeterminism monads) really powerful. - Cale

Cale Gibbard wrote:
important point is that the elements and structure of the collection are being constructed one at a time as you iterate over it, and they are easily garbage collected as soon as you're done with them.
OK, I kind of buy that argument. Though the very word "deforestation" indicates that it might work for structures other than left-biased lists... -- -- Johannes Waldmann -- Tel/Fax (0341) 3076 6479/80 -- ---- http://www.imn.htwk-leipzig.de/~waldmann/ -------

On Tue, Feb 28, 2006 at 08:52:00AM +0100, Johannes Waldmann wrote:
Cale Gibbard wrote:
This is exactly why lists are so common. If people ask why lists are so common in lazy functional languages, they ought also to ask why loops are so common in imperative languages.
A loop is a sequence of actions. In FP, I don't typically have actions, rather I collect values, so I'd need several collections types, among them: sequences, and among them: classical left-biased lists.
I think the point was that lists in haskell trancend just being a collection type or a data structure. They are a generally useful tool for composing functions. And like cale said, they often subsume loops and all sorts of other control constructs. I mean, even a for loop in haskell is done as mapM action [0..10] I'd say _most_ uses of lists are deforested away because they are used to express control and dataflow and arn't actually used as persistant structures. demoting them to just being a 'collection type' would be a disservice. That they can be used as a collection is a bonus, but not their main feature. the foldr/build trick for deforesting lists is really really cool! look at ghc core, it is amazing how all your lists just disapear and are turned into clever function compositions I would have never thought of :) John -- John Meacham - ⑆repetae.net⑆john⑈

John Meacham wrote:
I mean, even a for loop in haskell is done as mapM action [0..10]
I'd say _most_ uses of lists are deforested away because they are used to express control and dataflow and arn't actually used as persistant structures.
Yes, they are optimized away when ghc actually works. :) At the moment this seems to be broken (try length[1..n] in 6.4.1). -- Lennart
participants (12)
-
Ben Rudiak-Gould
-
Benjamin Franksen
-
Bulat Ziganshin
-
Cale Gibbard
-
Jim Apple
-
Johannes Waldmann
-
John Meacham
-
Ketil Malde
-
Lennart Augustsson
-
Malcolm Wallace
-
Marcin 'Qrczak' Kowalczyk
-
Taral