One problem is that Haskell collections are lazy by default. I'm aware of a few use cases where laziness lets you formulate a very
elegant recursive population of a collection, but I think that in
general, strictness is what you want,

While I strongly agree with the gist of your post, I never liked this particular kind of argument - different people have different needs.

and further, that if you want lazy, you store your data as one-tuples: data Lazy a = Lazy a
(If there's a workaround/solution in the other direction, I'd really
like to hear it).

That is the crunch point. If we can't choose between strict and
non-strict usage, the default becomes the only option, and it simply
doesn't fit all use cases. Workarounds are currently somewhat random:

- for indexed collections (which tend to be strict in the keys), one
   can use strict pairs when building from lists of pairs, but that
   doesn't work for all operations (at least not with the current APIs)
- some collections, for a few operations, provide strict alternatives,
   but that doesn't cover much of the API, and isn't even consistent
   over variations of the same kind of collection
- for some types, strict alternatives are provided as separate packages

That means that not only are the usage patterns different, for the
same problem, in different contexts (making switching types harder), but one can only get the solutions for some operations in some types: one is limited to the spartan and incomplete subset of the data type API that supports switching between strict and non-strict (check which
operations in Data.Map support both modes, then check what happens
when you want to move from Data.Map to Data.IntMap..). Not to mention that the strict alternatives aren't made obvious (you have to know that you should look for '-ed variants of operation names to get them, sometimes; that goes back all the way to the popular foldl with strict accumulator trap) and are documented in names instead of
specified in types.

Using a single set of packages and operations, with standardized
ways of instantiating a collection/type as either strict or non-strict, would be much better (well, usable, for a start;-), IMHO.

- using a strict default, optionally disabled via non-strict one-tuples,
   sounds workable (perhaps the one-tuple should be standardized,
   to give the compiler an indication that this is really an annotation,
   and to get similar benefits as from newtype deriving).

- using strictness annotations in types seems simpler, but has the
   drawback that the annotations are really not part of the types;
   types just happen to be a nice place to put annotations that
amount to invariants ('!t' in some context saying: "I always want whnf in this position, not unevaluated thunks") that the compiler
   can use for modifying strictness analysis and code generation.

- an in-between step might be to parameterize collections by a
   type constructor, with two pre-defined constructors ("Strict"
   and "Id") that effectively play the role of annotations while being
   proper parts of types (type constructors), thereby documenting
intent and enabling (pre-)specialization of code without creating programming or efficiency overheads (at least not for the library
   author - not so sure about library users).

One important point is composition of contexts: if one wants, say,
a "Data.Map.Map Key (Data.Map.Map Key type)", it should be
possible to specify that one wants both Maps element-strict, and
hence the composed structure strict in "type", without obfuscating the code with 'seq's or 'Lazy's all over the place. I can see that working with an annotation + code-generation/specialization approach, but how close would a type-tag approach come to supporting this?

I'm therefore tempted to suggest that collections should be
strict by default, and in particular, that there should be strict
arrays for arbitrary types, not just the ones that happen to be
unboxable. Unboxing should be an optimization for (some) strict
arrays.

Another suggestion I find myself agreeing with: when I'm looking
for strict variants of standard data types, unboxing is a natural
follow-on optimization, but if unboxing isn't possible, that shouldn't
keep me from getting the benefits of strictness where I need them.

Claus


_______________________________________________
Haskell-Cafe mailing list
Haskell-Cafe@haskell.org
http://www.haskell.org/mailman/listinfo/haskell-cafe

Reply via email to