Simon Marlow, a Haskell compiler and runtime developer, writes about GHC, concurrency, and Haskell at scale.
HN commenters recommend his book on parallel and concurrent Haskell, and cite his work on GHC's scheduler and Safe Haskell.
1. Such bad examples :( Tuples are data types you have to destruct, in every language. Somebody please show me a language where this doesn't require a tuple-to-function-argument translation:
sayHi name age = "Hi I'm " ++ name ++ " and I'm " ++ show age
people = [("Alice", 70), ("Bob", 30), ("Charlotte", 40)]
-- ERROR: sayHi is String -> Int -> String, a person is (String, Int)
conversation = intercalate "\n" (map sayHi people)
In python you have `*people` to destruct the tuple into separate arguments, or pattern matching. In C-languages you have structs you have to destruct.
2. And performance, you'd think a slow-down affecting every single function call would be high-up on the optimization wish list, right? That's why it's implemented in basically every compiler, including non-fp compilers. Here's GHC authors in 2004 declaring that obviously the optimization is in "any decent compiler". https://simonmar.github.io/bib/papers/eval-apply.pdf
3. Type errors, the only place where currying is actually bad, is not even mentioned directly. Accidentally passing a different number of arguments compared to what you expected will result in a compiler error.
Some very powerful and generic languages will happily support lots of weird code you throw at them instead of erroring out. Others will errors out on things you'd expect them to handle just fine.
Here's Haskell supporting something most people would never want to use, giving it a proper type, and causing a confusing…
> You still have to pass your logging code to every function that does logging right?
Yes, but no. Again, there are functional patterns you can apply.
First, functional programs tend to be more horizontally composed than vertically. Side effects usually happen at the same "level" of the application, rather than occuring at multiple depths. So the need to thread logging apparati is already reduced, simply because logging only happens higher up in the application.
Second, because side-effects only really occur at this thin layer, you can capture logging as a monadic action, avoiding the need to explicitly thread the logger state around. (This is the same situation as the "thread the world" problem we discussed earlier.)
Third, because so much more of your program is pure, it is much easier to perform unit tests and check assertions in other ways. At that level, logging is a bandaid over poor testing and assurance, not a goal in itself.
Finally, if you do find yourself deep down-stack with a need to perform some kind of side-effect, you can apply logical methods (a la LVars, CRDTs) to give a stateful interaction between separated parts of your program. The Haxl paper [0] is a good example of this: most of the design is pure functional, but for the part that involve an interaction not easily captured by the fundamental call-return / request-response pattern, they utilize monotonic state to maintain a registry of fulfilled and unfulfilled requests.
[0]…
Drawn in by the Emacs, learnt something new about C and C++, thank you for this! Very readable article for someone who doesn't feel too confident with low-level bits.
Btw, is this representation the reason why OCaml's ints are not as big as C ints?
Also interesting that the Haskell pointer tagging you link to[0] was done the way it was to avoid CPU branch misprediction, and that the old way which it replaced was "the source of half of the branch misprediction events". I wonder how "branch prediction friendly" current Haskell is.
[0] https://simonmar.github.io/bib/papers/ptr-tagging.pdf
A workaround for long compile times is trying to stay withing GHCi whenever possible and reload from there.
http://chrisdone.com/posts/haskell-repl
http://chrisdone.com/posts/making-ghci-fast
The Haxl team seem to use this strategy:
"Haxl users at Facebook do a lot of development and testing inside GHCi. In fact, we’ve built a customized version of GHCi that runs code in our Haxl monad by default instead of the IO monad, and has a handful of extra commands to support common workflows needed by our developers."
http://simonmar.github.io/posts/2016-02-12-Stack-traces-in-GHCi.html
The Haskell philosophy is easily misunderstood. It's not really that shared mutable state is evil but that it's difficult and so should be treated seriously and explicitly. The language still supports it—quite well. GHC's green thread scheduler is top-shelf. The main author of the GHC runtime wrote an excellent O'Reilly book called "Parallel and Concurrent Programming in Haskell" (which you can read for free online).
https://simonmar.github.io/pages/pcph.html
Of course that's not to say that Haskell is optimal for your work in embedded systems!
For completeness' sake, Haskell has had this for over a decade :) A SO thread that gives a nice overview [0], the paper [1], the library [2]. Here's how a parallel fib looks like:
fib :: Int -> Int
fib n
| n <= 1 = 1
| otherwise = x ‘par‘ (y ‘pseq‘ x + y + 1)
where
x = fib (n-1)
y = fib (n-2)
[0] https://stackoverflow.com/questions/958449/what-is-a-spark-in-haskell
[1] http://simonmar.github.io/bib/papers/strategies.pdf
[2] http://hackage.haskell.org/package/parallel-3.2.2.0/docs/Control-Parallel.html