What I Wish I Knew When Learning Haskell 2.0
dev.stephendiehl.com
dev.stephendiehl.com
http://www.cs.tufts.edu/~kfisher/teaching.html
https://www.inf.ed.ac.uk/teaching/courses/inf1/fp/ (P Wadler)
http://www.cs.princeton.edu/~dpw/cos441-11/schedule.htm
http://blog.davidterei.com/2011/10/stanford-haskell-course.h...
http://www.seas.upenn.edu/~cis194/
________________
also the Apress "Beginning Haskell" looks pretty good, tho the writing isn't perfectly clear. The example topics and sample code look good, and that's what mostly counts.
http://www.amazon.com/Beginning-Haskell-A-Project-Based-Appr...
The books by Hutton and Simon Thompson ("Craft of FP" 3rd ed)are good intros as well. Haskell school of music is really good, but not sure for people who aren't versed in music topics (harmony/theory, composition, MIDI, DSP.
http://www.amazon.com/Programming-Haskell-Graham-Hutton/dp/0...
http://haskell.cs.yale.edu/euterpea/haskell-school-of-music/
http://www.reddit.com/r/haskell/comments/23i6ih/announcing_c...
It's REALLY good and can be gone through quickly while still getting a feel for the "point" of Haskell.
I wouldn't call it either of those things. They are so general that people have a hard time getting them. That's all.
>That's what the "mumble mumble endofunctors" joke is getting at: sometimes they're a useful generalization, but more often they're a way to make easy things sound hard, or to make bad teachers sound smart.
That is not at all what the joke is getting at (and that doesn't even make sense).
Starting with the abstract just messes people up and gets them confused. You don't start throwing around Kleisli Arrows and endofunctors right off the bat.
They are easy to explain to 2nd or 3rd year math students.
The difference is in the audience's cognitive bias.
A (Haskell) monad has to be a (mathematical) monad. Haskell is counting on it not just to have certain function signatures, but to have certain properties - certain identities that it satisfies. This lets Haskell do some transformations under the hood. If you hand Haskell something that you say is a monad, and it doesn't satisfy the identities, you're going to get some really unpredictable results.
That's the part that's easy to explain to 2nd or 3rd year math students.
But, unless you're writing an application in group theory, as a programmer you don't really need a monad. You need something that will let you operate on a value that's in a Maybe, and therefore you need a monad. That is, you don't care about it being a monad for the sake of monads, you care about it as a way to solve a particular programming problem.
That's the part that's hard to explain to CS students. They just want to operate on a Maybe. They don't understand why they have to care about it being a monad, or what the connection is between "monad" and "a kind of smuggler that will smuggle this function in to operate on a value that's stuck in jail in a Maybe" (or whatever analogy they're using).
If you see a place my understanding can be improved, feel free to correct it...
The presentation here however just gives laws and usage examples. This approach makes it harder to give incorrect impressions to beginners.
Words confound the mind
Concepts will obscure the truth
Code alone brings forth
http://lpaste.net/103140https://www.fpcomplete.com/school/to-infinity-and-beyond/pic... Haskell Fast & Hard.
I think beginners get lost because they try to go too fast at first and get impatient.
For example, see below about the guy who tried to do GPU programming early on and got frustrated.
There isn't actually that much in the way of actual syntax in Haskell (LYAH is a great place to start, don't just try to eyeball random code snippets), and it becomes preferable after some familiarity.
What will take time is learning new concepts, programming purely and learning the tools to manage effects.
I've spent more than 20 hours on Haskell syntax and feel like I am not even halfway there to "doing useful stuff".
EDIT: And I think time to be able to do something useful is a very important benchmark because after that point, further learning takes little effort or motivation.
Some libraries may define their own operators (Scala suffers from this too), which can be impenetrable. Some libraries are better than others.
Although, I don't consider defining new operator names part of the syntax of the actual language itself.
I guess I'd just hope one doesnt start out jumping in to a complicated library. Its easier to start with more foundational materials.
When I struggle with Haskell it's more "I don't understand the type of this" than "I don't understand the syntax". Syntax is the easy part!
Then you should go ahead and start. The syntax is pretty thin. It's not what you're used to, but it's thin.
It's hard to describe what Haskell would be good for without sounding overenthusiastic, so I'll confine myself to saying that Haskell will expand your brain in ways that few other languages will. That said, Clojure is potentially one of those few other languages (depends on your personal development history, though IMHO enough stuff has been stolen from Lisp over the years that it's less of a mind-blow than it used to be), and you ought to learn about modern concurrency from at least one of Haskell, Go, Erlang, or Clojure (or possible Scala via Akka). Haskell opens you to the most correctly-implemented modern concurrency paradigms at once but any of those will get you most of the experience you need.
When one is fluent in Haskell I think the language naturally tends to drive you towards well-factored, reusable designs that can handle change well. I say "fluent" because I don't think one needs to be "expert" to get to this level, but certainly there's a level of experience required to be here. Beginners and pre-fluency intermediates in the language produce god-awful messes of code. (I know, because I have! Fortunately I had the foresight to do that to throwaway personal projects, rather than critical business projects.) I think a great deal of the Haskell propaganda is justified, but beware the Haskell enthusiast who recites the propaganda, but can't clearly explain in their own words the mechanisms that Haskell uses to accomplish it.
Actually, scratch Haskell out of that. Beware any tech enthusiast who can't give a coherent explanation in their own words of why the tech stack has the claimed effects, but just repeats the propaganda again.
First, is it Haskell that drives you toward well-factored, reusable designs? Or is it functional programming? (I know, you can't do Haskell without functional programming. You can do functional programming without Haskell, though. Can you separate the two, and say which one is driving you here?)
Second, have you ever seen Haskell deployed in a large, long-lived production code base? How large? How long-lived? How did it work? (I ask because I remember reading an article on Lisp, which said that X in Lisp is just like Y in an object-oriented language, "with the proviso that in Lisp, all the types exist in the programmer's head". I'm pretty sure that won't work very well on a million-line code base that lives for 30 years. Haskell has a very solid type system, so it won't run into that particular problem but... does it have a track record for large-scale work out in the real world?)
In my opinion, it's the purity, and the general simplicity of the individual pieces which forces you in certain directions. Since this conversation is dead-ish now, it's probably not a big deal to link to my longer musing on this: http://www.jerf.org/iri/post/2908 Even functional languages that make impurity easy and available end up making it too easy to jump down the escape hatch.
"Second, have you ever seen Haskell deployed in a large, long-lived production code base?"
I personally have not. As I said, where I work I tend to tamp down on any efforts to use Haskell, and there's not many! But I trust the people who have and do, and who knows, perhaps may join them someday.
As to where Haskell fits: you're correct, there are many other great languages out there. But I think Haskell is really unique in offering what it does. Clojure, Go, and Erlang are all great in their own regard, but none of them provides the degree of safety that Haskell does. For example, none of them enforce purity, and all three of them freely allow the use of `null` values (which is really quite surprising considering how many problems nulls cause). Clojure and Erlang avoid mutable state like Haskell does (though not to the same degree of rigor) but don't provide the elegant solutions for simulating mutable state that Haskell does with its monads, which almost allow you to "have your cake and eat it too" with state. None of them encourage the degree of abstraction that you'll find in Haskell. Also, at least according to the Computer Benchmark game, Haskell beats all three of them in terms of speed, as well. Haskell's "place" is in generating highly robust code with good performance, and also works as a beautiful language for expressing certain mathematical concepts.
This doesn't make Haskell a perfect language, but it absolutely has a lot to offer that other languages don't. And it's so much different than other languages, that at the very least it can change your perspective on a lot of things.
Of course you can tweak things to be fast in hakell, but to be able to actually program haskell in such a way to, beat other languages in performance, is not a trivial skill.
There are numerous stackoverflow and r/haskell posts "Why is my haskell code 10x times slower than language X?" Or observe this set of web framework benchmarks: http://www.techempower.com/benchmarks/
Yes, my point was just that there are programmers using Haskell in a non-optimal way. Beginners seem to be surprised when their Haskell code isn't automatically in the upper level.
I'm not really a great fan of benchmarks, or this particular benchmark, but at least the source of every test is available:
https://github.com/TechEmpower/FrameworkBenchmarks/blob/mast...
https://github.com/TechEmpower/FrameworkBenchmarks/blob/mast...
https://github.com/TechEmpower/FrameworkBenchmarks/blob/mast...
Also true of the benchmarks game. (Also the command line used to compile and run the program, etc)
http://www.willamette.edu/~fruehr/haskell/evolution.html
Is this "idiomatic": http://benchmarksgame.alioth.debian.org/u64q/program.php?tes...
"One can, with sufficient effort, essentially write C code in Haskell using various unsafe primitives. We would argue that this is not true to the spirit and goals of Haskell, and we have attempted in this paper to remain within the space of “reasonably idiomatic” Haskell. However, we have made abundant use of strictness annotations, explicit strictness, and unboxed vectors. We have, more controversially perhaps, used unsafe array subscripting in places. Are our choices reasonable?"
"Measuring the Haskell Gap" http://www.leafpetersen.com/leaf/publications/ifl2013/haskel...
http://benchmarksgame.alioth.debian.org/u64q/program.php?tes...
FWIW, I don't Don Stewart[0] would consider that idiomatic either. He just knows a bit more than the rest of us about how to squeeze more performance out of GHC-compiled code and was willing to use non-idiomatic code to do so.
Also FWIW, personally I don't think language shootouts are very informative most of the time, but... IMO they should at least use idiomatic code -- otherwise they tell you even less than they already do about what real-life performance is like.
[0] Just an example, I know he didn't contribute the particular benchmark you linked to. He did contribute a few of the other programs AFAIK. I think the following sentences also apply to (essentially) all the Haskell shootout contributors.
This otoh: http://benchmarksgame.alioth.debian.org/u64q/program.php?tes...
>Foreign.Ptr (and companions) are hardly ever used in normal Haskell code unless you're optimizing for that last bit of performance<
Do you think "optimizing for that last bit of performance" doesn't happen in the wild?
>IMO they should at least use idiomatic code<
Provide some!
http://benchmarksgame.alioth.debian.org/play.php#contribute
GHC 7.8.2 installed, cabal 1.2 stuff available as-needed.
Of course it does, but the cost increases non-linearly and you have to find the right trade-off for the situation at hand. Do you want maintainability or absolute performance?
> Provide some!
Of course one could, but that would that accomplish? Everyone who thinks these benchmarks have value just looks at the fastest-performing version of $YOUR_LANG_BENCHMARK. Hint: Mostly it won't be idiomatic regardless of the value of $YOUR_LANG.
Do you think the source code for those optimized for the last bit of performance programs tells us something about that?
>Of course one could, but what would that accomplish?<
Could one? Perhaps there'd be complaints that your "idiomatic programs" were just naïve.
A passive website like the benchmarks game can never be enough, by itself, to educate and inform all (or even most) readers -- reading selectively is far too commonplace and errare humanum est.
The benchmarks game is a resource. There are enough examples that when someone draws a misinformed conclusion, we can usually show them a counter-example and ask them to think.
But only if someone contributes those (whatever they consider idiomatic to be) idiomatic programs as counter-examples.
No kidding:
http://benchmarksgame.alioth.debian.org/u64/program.php?test=regexdna&lang=ghc&id=2
It's mostly a matter of knowing C, then knowing how to torture GHC into doing what the C would do.Please contribute your Haskell regex-dna program that does not do anything like that and get's "good" performance.
Here's the task description: http://benchmarksgame.alioth.debian.org/u64/performance.php?...
Here's "How to contribute programs": http://benchmarksgame.alioth.debian.org/play.php#contribute
GHC 7.8.2 installed, cabal 1.2 stuff available as-needed.
No. I stopped bothering with that cluster fuck like a decade ago. It is a complete waste of time.
It actually is trivial in a lot of cases. It's often as simple as swapping out lists for vectors and inserting a few strictness annotations into your types.
The good news is that it stops at the function boundaries. Your quicksort may be implemented in the ST monad, looking like some bastard child of C, but the function type ends up being
qsort :: Ord a => Vector a -> Vector a
And that's one of the likeable properties of Haskell: it doesn't deny that real-world programs sometimes require mutability, but allow you to isolate it through the type system. So, even though qsort may use mutable memory for sorting, it is a pure and referentially transparent function.http://benchmarksgame.alioth.debian.org/dont-jump-to-conclus...
Erlang's syntax is very similar to Haskell's, is it not?
If you just want to learn a FP language, I'd go with Haskell because you learn a pure FP language that teaches you all the concepts that you will encounter in half-breed languages :) .. people will love me for that comment.
The difficulty one might find comprehending Haskell is probably because of the semantics rather than syntax - particularly if you have a background in languages like C++ and Java - the first thing you'll be attempting to do is find parallels between this new obscure syntax and what you already know - but they don't necessarily fit, because the language is fundamentally different.
A common example of misunderstanding for beginners is function signatures - in Haskell, one simply defines a function name, followed by two colons, followed by a type declaration which states the function's type. It's as simple as
add :: Int -> Int -> Int
If you're attempting to draw the parallel to the equivalent in another language such as C#, you might think this is the same as int Add(int a, int b) {
...
}
While pretty close, the Haskell equivalent of that is really a coupled argument. add :: (Int, Int) -> Int
The C# equivalent of the original is closer to this Func<int, Func<int, int>> add = a => (b => ...);
At this point it should become a bit more obvious why that "cryptic" function signature is the way it is - it's because all functions in Haskell are secretly single argument functions, and additional arrows indicate that the return type is another function. In other words, the arrow, -> associates to the right, which makes it unnecessary to parenthesize the returned functions - but you can optionally write them for clarity: add :: Int -> (Int -> Int)
As a result, you can partially apply add with a single value, e.g. `add 5`, and a valid function which adds 5 to its argument is returned, e.g. `(add 5) 6 == 11`, and because function application (denoted by a space) associates to the left, you can omit the parens and simply say `add 5 6`.Given that you have some Erlang background, the syntax for implementing functions should not be foreign to you, because they're quite similar, except perhaps "do-notation", which will won't be very intuitive until you grok monads.
For example, this is the default implementation of foldr, in the Traversable typeclass, in terms of a foldMap implementation:
foldr :: (a -> b -> b) -> b -> t a -> b
foldr f z t = appEndo (foldMap (Endo . f) t) z
This does not take long to grok in Haskell, but translating it to any imperative language would make no sense. It requires an understanding of the semantics of Monoids, currying, function application, and Endofunctors. None of these are hard, but none of them exist in more mainstream imperative languages.But heck, this is at least something like what it is doing:
#!python3
from functools import partial
def foldr(f, z, t):
for endo in (partial(f, i) for i in t):
z = endo(z)
return z
Only, um, not quite, because iterables are not exactly foldables, it lacks the generality of actually calling the foldMap function (which can be different in different types), it has no type signatures so it is less strict about its parameter, and this is cheating a bit because python already has nice functional programming tooling compared to many other languages, and so on. But the jist is there, and even some of the performance qualities of lazyness if you get how iterators work in python.I personally suggest you just learn it from scratch. Its not just a different skin over the same ideas, like so many other languages.
(I know this is just a short demo rather than production code, but all the "good examples of haskell" I saw at university were like that too, and much of the online documentation I saw :/ )
foldRight :: (accum -> item -> item) -> accum -> foldable item -> accum
foldRight combiner initialAccum foldables = appEndo (foldMap (Endo . combiner) foldables) initialAccum
... that didn't help. After all, each variable was used once, and there were only 3 variables in the entire scope. Most functions are like that, and you can easily keep track of it without breaking a sweat. What is important to keep track of is what types those are, and what functions are valid for those types, and how those types are changed with function application and whatnot.So its not just because of the test example. I actually copied and pasted the original out of the standard library, so it is about as canonical as it gets. The best possible description for f is (a -> b -> b), and while you might hand wave it as a "combiner" or "accumulationStep" or whatnot, that does almost nothing to clarify how this function works.
The cultural choice is not arbitrary. The real cultural difference is in the level of abstraction so often being "above" the semantics. In java, or python, or whatever else, I will religiously use nice, expressive names. But the short names, and other even crazier things like pointfree syntax actually feel somewhat more natural in Haskell.
I think this is what makes Haskell so difficult initially. Everything is abstracted to the extreme. Explanations must either stick to the abstractions (making it difficult to grasp), or give concrete examples (necessarily limiting the scope of what is learnt).
Going back to the foldr example, its actually not that bad. The f is already clearly a function (from name and type) and the full type of foldr is [1]
foldr :: Foldable t => (a -> b -> b) -> b -> t a -> b
so you actually are told what the `t` is (its a Foldable).
I could see them using `zero` instead of `z` but its a 3 line function that does what you would expect from the name so its not that big of a deal. TBH, the bigger problem is having a folding abstraction in the first place. If no one ever teaches you about it, its hard to get the point just by reading the source code but once you know about what a fold is you don't need to actually look inside the implementation that much.That said, there are cases where I do think that longer variable names would help a bit. For example, you might come across types with 5 or 6 type parameters like [2]
data Pipe l i o u m r
and I think in these cases the more descriptive names like in [3] newtype Form m input error view proof a
might help a bit. Its still a tradeoff though - types get harder to read if they get too long and types show up all over the place so they tend to need shorter names than variables.[1] http://www.haskell.org/hoogle/?hoogle=foldr
[2] http://hackage.haskell.org/package/conduit-0.5.0/docs/Data-C...
[3] http://www.happstack.com/docs/reform-0.1.1/doc/html/reform/T...
After some experience, I think the biggest pain point in Haskell's syntax is that it is very light on keywords so many things that would show up as syntax errors in other languages might show up as type errors in Haskell. For example, forgetting a comma in a list will result in a function application between the two list elements instead of a syntax error.
Haskell doesn't have a cryptic syntax. The same complaint would be just as (in)valid leveled at any commonly used language. We're not talking APL here.
>There are so many great languages out there nowadays, Clojure, Go, Erlang.
Go does not belong in that list.
>I don't see where Haskell would be a better fit
For writing software. I realize that sounds snarky, but that really is the answer. To see the problem with your question, just turn it around. Where would clojure or go be a better fit than haskell?
One of Haskell's unique selling points is the fantastic ease with which you can build software that is correct-by-construction and concise at the same time.
For someone I'm mentoring, I recently came up with two examples of this [1][2].
I don't have time ATM to explain all the details (maybe someone else does), but based on them I intend to write in the near future a blog post titled "Using the expressive power of Haskell's type system to build software that is correct-by-construction". I'll submit the link to HN when that happens.
It will be part 3 of this[3] ongoing series.
[1] https://gist.github.com/dserban/11176875 [2] https://gist.github.com/dserban/11139419 [3] http://techblog.rosedu.org/haskell-part2.html
data Inches = MkInches Double deriving (Eq,Show)
There is newtype for this. (MkInches u) \+/ (MkInches v) = MkInches ( u + v )
First two pairs of parens are redundant. from_inches_to_cm vin =
MkCm (vin_as_pure_double * 2.54)
where
(MkInches vin_as_pure_double) = vin
Surely you want: inchToCm (MkInches x) = MkCm (x * 2.54)
In Haskell we use camelCase.Besides these fairly trivial mistakes there are simply better approaches for dealing with units.
For example, I wouldn't tell someone to write a web server backend in Haskell if it's going to be in production. Sure, you can do it, but there are more adequate tools for the job. On the other hand, if you were looking to create a DSL or write a parser, Haskell would be great. This is how Galois and Facebook are taking advantage of Haskell.
This has been my personal observation since following HN for the past few years.
I think the idea though that one language is objectively better at a certain task gets strongly warped by subjective factors. Ultimately language decision is more about the team of programmers you have, the available libraries and ecosystems, and the team's understanding of it.
Perhaps not surprisingly, we have found that for programmers already experienced in Haskell, Haskell is a great language for most problems.
I agree. Modern languages are converging on ideas more than ever so it starts to become subjective. However, there exists a subtlety in feature focus. Compare concurrency and distributed programming in Erlang and Haskell, for example. If you're proficient in both and your domain demands high concurrency, why would you choose the lesser of the two which is Haskell?
You give no support to the claim that "There are more adequate tools than Haskell for writing web server backends".
http://www.techempower.com/benchmarks/
https://github.com/TechEmpower/FrameworkBenchmarks/blob/mast...
However, as a user of both Haskell and the JVM, there are some things to be said for the Java library ecosystem. For instance, the most widely-used Java libraries are often more mature, maintained better (usually because its authors are paid to do so), and more API-stable (for most of my Haskell code, a dependency version bump usually implies updating my own code). Of course, this is all reasonable, since the Haskell ecosystem exploded far more recently than Java's. The older Haskell pages (bytestring, text, vector) tend to be very stable and predictable.
Another aspect where the JVM is currently ahead is VM monitoring. E.g. if there is a bottleneck, I can easily attach Visual VM or even my IDE to a running Tomcat and peek around. The JVM can do code hotswap (even more effectively with JRebel and LiveRebel).
Note: I posted this to provide some balance to the discussion. I believe Haskell is superior in many other respects, such as the type system, REPL-driven development, web frameworks with type-safe URLSs, etc.
You probably shouldn't be giving advice then.
>This is how Galois and Facebook are taking advantage of Haskell
Facebook is using haskell for a high performance, high concurrency server for filtering. You do realize how similar that is to a web server right?