Haskell “collections” language design

前端 未结 5 1876
耶瑟儿~
耶瑟儿~ 2021-02-07 03:21

Why is the Haskell implementation so focused on linked lists?

For example, I know Data.Sequence is more efficient with most of the list operations (except for the

5条回答
  •  清酒与你
    2021-02-07 03:56

    Before getting into why, here's a summary of the problem and what you can do about it. The constructors [] and (:) are reserved for lists and cannot be redefined. If you plan to use the same code with multiple data types, then define or choose a type class representing the interface you want to support, and use methods from that class. Here are some generalized functions that work on both lists and sequences. I don't know of a generalization of (:), but you could write your own.

    • fmap instead of map
    • mempty instead of []
    • mappend instead of (++)

    If you plan to do a one-off data type replacement, then you can define your own names for things, and redefine them later.

    -- For now, use lists
    type List a = [a]
    nil = []
    cons x xs = x : xs
    
    {- Switch to Seq in the future
    -- type List a = Seq a
    -- nil = empty
    -- cons x xs = x <| xs
    -}
    

    Note that [] and (:) are constructors: you can also use them for pattern matching. Pattern matching is specific to one type constructor, so you can't extend a pattern to work on a new data type without rewriting the pattern-matchign code.


    Why there's so much list-specific stuff in Haskell

    Lists are commonly used to represent sequential computations, rather than data. In an imperative language, you might build a Set with a loop that creates elements and inserts them into the set one by one. In Haskell, you do the same thing by creating a list and then passing the list to Set.fromList. Since lists so closely match this abstraction of computation, they have a place that's unlikely to ever be superseded by another data structure.

    The fact remains that some functions are list-specific when they could have been generic. Some common functions like map were made list-specific so that new users would have less to learn. In particular, they provide simpler and (it was decided) more understandable error messages. Since it's possible to use generic functions instead, the problem is really just a syntactic inconvenience. It's worth noting that Haskell language implementations have very little list-speficic code, so new data structures and methods can be just as efficient as the "built-in" ones.

    There are several classes that are useful generalizations of lists:

    • Functor supplies fmap, a generalization of map.
    • Monoid supplies methods useful for collections with list-like structure. The empty list [] is generalized to other containers by mempty, and list concatenation (++) is generalized to other containers by mappend.
    • Applicative and Monad supply methods that are useful for interpreting collections as computations.
    • Traversable and Foldable supply useful methods for running computations over collections.

    Of these, only Functor and Monad were in the influential Haskell 98 spec, so the others have been overlooked to varying degrees by library writers, depending on when the library was written and how actively it was maintained. The core libraries have been good about supporting new interfaces.

提交回复
热议问题