reddit
reddit is a source for what's new and popular online. reddit learns what you like as you vote on existing links or submit your own!
Ask Reddit: What is Haskell good for? (programming.reddit.com)
74 points posted 7 days ago by speciousfool

(log in to vote on this article, comment on it, or share it with friends)

info comments related details

sort by

style

You are viewing a single comment's thread. Click the comments tab above to view all of the comments on this link.
cgibbard 27 points 7 days ago *

It's kind of a difficult question to answer -- what any given general-purpose language is good for. There are a few things that Haskell is particularly good at (as other people have pointed out to some extent), but it's really the general aspects of the language which make it pleasant to program in, rather than it being particularly well suited to certain applications.

There are some more general things which it is particularly good at, but which aren't directly application domains. These are things which might be useful in just about any context. I'll list a few:

  • Separating effects from code which only runs to produce a result. This is sublime beyond words in any language. Okay, perhaps that's a slight overstatement. It allows you to reason about and test code in ways that you just couldn't in most languages. You just don't realise how much work you're doing thinking about state until it's suddenly gone and you don't have to worry about it, or at least have control over the amount to which you do.
  • It has a type system which is genuinely useful for eliminating wide classes of bugs in your code, but which largely doesn't get in your way. You don't have to write type signatures when you don't want them, and the type system is sufficiently expressive that it's rare to feel unduly constrained by it. There's a very noticeable reduction in time spent debugging programs which actually compile thanks to this type system. Having a significant portion of your bugs caught and pointed out by the compiler before you even start to run your program is great. It lets you focus on testing the really interesting properties of your code, rather than hundreds of boring details.
  • It's lazily evaluated. The benefits of this are perhaps even harder to get across in a short blurb than the others I've mentioned, but they're just as important. Persistent lazy evaluation with good library support (that is, library functions are lazy when they can be) allows you to break up problems in ways that you just can't do in a strict language. It allows you to reuse code in places where you'd otherwise have had to tear it apart in order to combine functionality in a way that still performs reasonably, or at all. A good paper on this (though it still takes a few reads through and a bit of hands-on experience to really get it), is Why Functional Programming Matters , by John Hughes.
  • Support for programming with concurrency and parallelism is already excellent, and improving in exciting ways which you won't see in other languages for a while. Between STM for task parallelism, parallel evaluation annotations, and the new nested data parallelism support (in development GHC right now), there's a lot of really effective and powerful tools for getting your programs to work well on multiple cores.

This is just a few of the things I could say about it. Picking specific applications is a confusing way to express what the language is good at, because the things at which Haskell really excels would apply to almost any program.

If I had to summarise, it's particularly good for programs which need to be correct (who really likes bugs anyway?), and also those which need to use concurrency or parallelism.

permalink
queensnake 10 points 1 day ago

Doesn't using the data structures demand a complete tearing out and remodelling of your mind, though? Personally I've gotten through the smaller stuff (zip, foldr, blah, heavy recursiveness) but I started stalling at having to relearn from scratch, how to go about manipulating the usual data structures.

permalink parent
cgibbard 155 points 1 day ago *

Oh, certainly. You really do have to take a second look at everything. Data structures in a lazy language are (or at least can be) a whole lot more like control structures, with the list taking the place of the loop.

Learning Haskell generally demands a complete tearing out and remodelling of your mind, if you're coming from an imperative background. It's worthwhile though, because you end up with a much cleaner perspective on everything.

One of the most basic things which you can do with an algebraic data structure is to replace all the data constructors in it with functions of appropriate arity and type, but which instead of producing that datatype again, might produce some other type of value. A function which does such a replacement is known as a 'catamorphism'. For lists, this is the right-fold:

           
            foldr :: (a -> b -> b) -> b -> [a] -> b
foldr c n []     = n
foldr c n (x:xs) = c x (foldr c n xs)
           
          

It recursively replaces each cons (:) in the list with the function c , and the nil [] at the end with the value n . So, foldr (:) [] is the identity function, and foldr (+) 0 is the function which adds up the elements of the list. Note that in the case that the list is nonempty, foldr immediately returns c applied to some parameters. If c happens not to need its second parameter in order to construct part or all of its result, the recursive call to foldr might never be evaluated (lazy evaluation is outermost-first).

For another example consider the following tree type:

           
            data Tree a = Tip | Branch a (Tree a) (Tree a)
           
          

In case you have trouble reading that, it basically says that a value of type (Tree a) is either the constant Tip , or it is of the form (Branch x l r) , where x is of type a , and l and r are both values of type (Tree a) . For instance:

           
            (Branch 0 (Branch 1 Tip (Branch 2 Tip Tip))
          (Branch 3 (Branch 4 Tip Tip) (Branch 5 Tip Tip)))
           
          

represents the tree:

           
            0
       / \
      /   \
     1     3
      \   / \
       2 4   5
           
          

We can write a catamorphism (fold) for this type as follows:

           
            foldTree :: b -> (a -> b -> b -> b) -> Tree a -> b
foldTree t b Tip = t
foldTree t b (Branch x l r) = b x (foldTree t b l)
                                  (foldTree t b r)
           
          

Suppose we wanted to write the function which gives a preorder traversal of the tree, as a list. We could write that as follows:

           
            preorder :: Tree a -> [a]
preorder = foldTree [] (\x l r -> x : (l ++ r))
           
          

This replaces the tree structure with list structure in the appropriate way. One can also write this as:

           
            preorder :: Tree a -> [a]
preorder t = foldTree id (\x l r -> (x:) . l . r) t []
           
          

which first replaces the tree structure with function structure, and then applies the resulting function to the empty list. This has better performance because it avoids repeatedly appending with (++) (which takes time on the order of the size of the left parameter).

Or to sum up the elements of the tree:

           
            sumTree = foldTree 0 (\x l r -> x + l + r)
           
          

Or to compute the maximum of 0 and the elements of the tree:

           
            maxTree = foldTree 0 (\x l r -> x `max` l `max` r)
           
          

Or how about flipping the tree over left to right?

           
            flipTree = foldTree Tip (\x l r -> Branch x r l)
           
          

Or once we get into dealing with monads (you might feel like skipping this one, down to the horizontal line, if you haven't seen anything about monads yet), the tree might hold actions to be performed, say, returning the value Left u if the left branch of the tree is to be followed next, or Right u for the right branch.

           
            runTree :: (Monad m) => Tree (m (Either a b)) -> m [Either a b]
runTree = foldTree (return [])
                   (\x l r -> do v <- x
                                 case v of
                                   Left u  -> liftM (v:) l
                                   Right u -> liftM (v:) r)
           
          

Here, we replace tree structure with program structure. The result is a way to run a sort of decision tree, each action deciding which of the child trees should run, and collecting up a list of the results.


So the general idea to take from this is that data constructors can be thought of as temporary place-holders to be filled in with other functions later, and data structures would then represent whole control flows waiting to happen.

Another issue is that immutable data structures can require a little more thought to design in the first place, but when well-designed generally can compete quite favourably with mutable ones performance-wise (typically with only a logarithmic factor performance hit). There are some fairly general solutions in this regard, such as finger trees (it's a package on Hackage), with which it's possible to implement an almost-magically-fast Sequence , and things like priority queues (which unfortunately haven't made their way into the libraries yet).

Also, Data.Set and Data.Map provide very good implementations of general sets and finite maps using binary balanced trees.

Of course, you can do mutable data structures in Haskell too, using STRefs or IORefs in place of pointers, but you tend to want nice immutable ones because they're so much simpler to reason about (even if they're slightly more work to define, if you can't find a way to build on other structures that already exist).

Personally, I've found the stuff that's already in the libraries provides pretty decent coverage for most applications, in terms of low-level structures that you might need.

permalink parent
Korollary 48 points 1 day ago

You have comments that are more detailed than most people's blog posts. Get a blog or else the baby seal gets it.

permalink parent
fsp 10 points 1 day ago

What would a baby seal do with a blog?

permalink parent
jleedev 9 points 1 day ago *

Well, you can change BLOG to CLUB in four steps:

  1. BLOG
  2. BLOB
  3. BLUB
  4. CLUB

Scary, isn't it?

permalink parent
ajrw 4 points 15 hours ago

That looks like three steps to me.

permalink parent
babyseal 27 points 1 day ago

Please do what he says mister! I think he really means it!!!

permalink parent
queensnake 11 points 1 day ago *

Thanks much, I appreciate that.

edit: Note, all, the one book I've seen on this .

permalink parent
pl0nk 10 points 7 days ago *

You just don't realise how much work you're doing thinking about state until it's suddenly gone and you don't have to worry about it, or at least have control over the amount to which you do.

Amen.

I'm working on modifying a complex algorithm right now. It has no formal specification and only one reference implementation, written in C++ by a guy who naturally no longer works here, and which does lazy evaluation and caching in order to be useful on actual datasets.

The actual algorithm is quite hard to discern among all the carefully ordered state changes, and due to the fact that caching isn't built as a layer on top, but in fact threaded throughout the computation as incremental side effects. There is not a good separation of concerns . Since it's so difficult to reason about, trying to fix it feels like reaching inside and jiggering the wires. "Seems to be working now, ship it!" Ugh.

My ideal scenario would be to have enough time to write a purely functional reference implementation and use that as a baseline for (a) changing the algorithm in the first place, (b) unit-testing the crazy C++ one. Oh, luxury of time...

permalink parent
augustss 4 points 7 days ago

Well put, as always.

permalink parent