Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

I'm in the same boat as you. I learning Haskell earlier this year, and while I really really want to like it (if nothing else, I'm a sucker for type systems) and even start using it for projects I can't when, IMO, there are languages out there that feel so much more productive to me. A few key concerns I have with the language and community:

1. I feel like there is a really strong push towards writing concise code, which make it really hard to approach for me. I've seen a lot of code which substitues words as function names for 2 or 3 character symbols. I'm okay with learning `>>=` and the like, but when every library want to invent its own symbol-functions, I feel like the barrier to entry for me becomes much higher. This occours in just about every high profile library I've seen, from parsers to web frameworks; everyone wants to invent their own DSL.

2. For as much as Haskell seems to pride it self on function composition I don't think `.` is the best way to do so; Clojure's threading macros are much more readable to me. Comparing the thread-last macro ``` (->> (range 10) (filter odd?) (map #(* % %)) (reduce) ``` to Haskell's composition operator `(sum . (map (\x -> x * x)) . (filter odd)) [1..10]`. (Please correct me if there is a clearer way to write this.) The former is much more readable from and LTR English language PoV. In Haskell I need to start from the right and work my way left; and this become even more harder for me to decipher when function operator precedence starts coming into play. It's small quality of life things life this which makes all the difference IMO.

3. The millions of syntax extensions in ghci, some of which are incompatible with each other. This means ProjectA and ProjectB can be written in completely different, possible incompatible, 'dialects' of Haskell. Now I have to learn the base Haskell languge and whatever language extensions that project has dedcided to use just so I can start reading and understanding the code.

4. AFAIK, there is now easy way to get function documentation in the repl. Sometimes types aren't enough for me and I would like to have some human written documentation guide me when I'm trying to use a library.



Your example can be written more clearly (IMO) in Haskell like this:

  sum $ map (^2) $ filter odd [1..10]
Or, defined as a function:

  > let foo = sum . map (^2) . filter odd
  > foo [1..10]
  165
You can write it without using the composition operator at all, if you prefer:

  > let bar xs = sum (map (^2) (filter odd xs))
  > bar [1..10]
  165
In general, you can write out your programs as crazy sequences of operators (whose precedence may not be obvious to the reader):

  > take 5 . unfoldr (Just . next) <$> getStdGen >>= putStrLn . show
  [1336079013,234736121,108049288,410228860,1573295749]
But you don't have to:

  > let infiniteRandoms = unfoldr (Just . next) :: StdGen -> [Int]
  > gen <- getStdGen
  > let fiveRandoms = take 5 (infiniteRandoms gen)
  > putStrLn (show fiveRandoms)
  [1336079013,234736121,108049288,410228860,1573295749]
People seem to think Haskell is all about writing your whole program in a single hideous line of code with incomprehensible sequences of arcane operators glued together by a glut of anonymous functions, but that's just bad code.

As for left-to-right vs right-to-left, I have never had a problem with the way Haskell does it. I think if you're coming from a language that does it differently, it makes sense that Haskell would trip you up in that regard. Haskell's composition operator matches the way it's usually written in mathematics, but there's nothing special about it and you could change it to go the other way if you really wanted to (but please don't):

  > let g . f = \x -> f (g x)
  > let foo = filter odd . map (^2) . sum
  > foo [1..10]
  165
`(>>>)` from `Control.Arrow` is a more reasonable way to get that sweet left-to-right action but using it might still throw people off:

  > let foo = filter odd >>> map (^2) >>> sum
  > foo [1..10]
  165


To add to that from someone who went into programming after math grad school, composition is actually much more natural in the way to think about functions. When putting together functions, and where they map to, it is more natural to think about functions the way Haskell thinks about them, but more natural when writing them to go the other way at first. This is because when one is working at the lower level of just writing code it becomes easier to move from one data state to another, but at the high level, you lose some flexibility in seeing the high level structure and it becomes more difficult to figure out how to move things around if it turns out you have to modify the code.

In math, the preferred way of writing functions is via composition, as then it enables tools of visualization such as commutative diagrams. One thing to note is that in general mathematicians have a history of picking the right choice in notations for thinking about abstractions at an intuitive level in the world of mathematics (when there is a choice and it matters), and IMO they were correct about how they chose to represent function composition, although it is not immediately obvious unless you start talking about function domains and ranges - the composition notation matches the flow of reading how functions map values from one set to another.

That Clojure version reads like a complete mess to me personally.


> The former is much more readable from and LTR English language PoV. In Haskell I need to start from the right and work my way left;

For short chains, pronouncing `.` as "of" works well in English, e.g. `sum . tail $ foo` is "the sum of the tail of foo`, and it follows the same order as nested function application: `sum (tail foo)`.

I agree the the other way around, e.g. a "then" operator `>>>`, is easier to understand with long chains: with `f . g . h . ...` we have to remember `f`, `g`, `h`, etc. in a "mental stack", until we get the the end. With the other way round, `... >>> h >>> g >>> f` we can "mentally apply" each function to a "mental accumulator" as we encounter it.


> The millions of syntax extensions in ghci, some of which are incompatible with each other. This means ProjectA and ProjectB can be written in completely different, possible incompatible, 'dialects' of Haskell.

To my knowledge, there are no GHC extensions that are incompatible on a project level (that is, you could have one enabled in one file, another enabled in another, and it would break things). I'm much less confident that there are no extensions that are incompatible if you try to use them within the same file, but I am not able to think of any off the top of my head.


Operation precedence in Haskell is hard to get used to, but it leads to very clean code after you get used. Try:

  sum . map (\x -> x * x) . filter odd $ [1..10]
Function composition leads to a very stable right-to-left syntax. It also takes some practice, but in Haskell you almost never have to go hunting for operators or remember if something is infix or prefix (except for the shameful single argument '-', as in '-1').




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: