What I Wish I Knew When Learning Haskell
dev.stephendiehl.com
dev.stephendiehl.com
Just writing simple efficient matrix multiplication is a pain. It took me a couple of days to write a working quicksort.
I couldn't find any resources that provide a very serious introduction to optimizing Haskell code.
I found out way too late in my adventures with Haskell that Monad Transformers and similar abstractions have a significant runtime overhead and aren't free, I thought I was just playing with types that won't get in the way when the code compiles.
No one seems to cover this aspect of Haskell.
Don't use String. Use Text for Text, ByteString for raw bytes.
Related to String: don't use lists for large datasets unless you're intentionally modeling and reasoning about your data as an infinite space. Use Vector if it's finite, fits in memory, and you're going to comprehend it all at once. Vector gives you cache-friendliness as well.
As a safe default: lazy in the spine, strict in the leaves.
Use streaming libraries (Pipes, Conduit) for processing large datasets. I did a test with a csv parsing library and compared the non-streaming (default) interface and the streaming interface provided by pipes-csv for summing columns in a ~6mb CSV.
No streaming used 30mb of heap.
Naive streaming used 10mb.
Pipes used 600kb.
monad-control and mtl (monad transformer libraries) are pretty fast and can be used without much (any?) worry in 99.999% of circumstances.
Use attoparsec and be mindful of backtracking anytime you're doing perf sensitive parsing. If you hit a limit, you may need to use a parser generator but this is almost never the case. If you need to parse something huge...use a streaming parser.
Sometimes CPS transformation can be a huge boon. Read what Bryan O'Sullivan was written about this for the parsing side of it. Kmett has good examples of CPS transformation for performance in his libraries as well.
Use async.
Default to using TVars (STM containers) for correctness until you know what specific properties you need. If you get perf sensitive but still want transactions, turn the scope of your transactions into cells of a data structure. Cf. http://hackage.haskell.org/package/stm-containers when you have composable concurrency abstractions, it can become just yet another data structures problem.
If you're writing a (very) hot loop, you'll probably end up looking to avoid boxing (just like Java) and touching the heap (just like Java, C, C++). Don Stewart has written good, thorough examples of this.
Resources:
Anything Don Stewart has ever written
Kmett's libraries and blog
Bryan O'Sullivan's libraries (particularly attoparsec and aeson) and blog
Johan Tibell is the strictness perf honcho. Check his libraries for ideas if you're making something strict. Don Stewart has written along these lines too.
http://book.realworldhaskell.org/ is out of date but the stuff on perf and debugging are some of the best in the book.
My own book http://haskellbook.com/ will explain how to reason about performance and laziness, though it's primarily a practical beginner's book. Focus WRT perf will be more on understanding the foundations, how things evaluate, how the runtime works so you can absorb all the other information as easily as possible.
Koalafications:
Every backend I work on in ad tech, including the public facing adserver, is written in Haskell. Our adserver latencies range 12-17ms with some pretty gentle 99th percentiles. The average DSP we talk to has response times in the low hundreds (100-400).
Mostly the stuff touching libraries will break but if you know where to look on Hackage you can fix it.
I am a freelance computer programmer from India. I have been teaching Haskell myself for the past 3-4 months. I have stopped for a while, because I am hard pressed to find a job where I can work remotely. Can you look at some code I have written and let me know if I can put Haskell as something I can put in my resume? If yes, what is the best way to put it.
Here is the code.
1. A simple neural network with back propogation algorithm that recognizes alphabets letters on a 8x8 grid.
https://bitbucket.org/sras/haskell-stuff/src/default/snn.hs?...
2. A port (partial) of my side project web application in PHP/MySQL to Haskell using Spock framework.
https://bitbucket.org/sras/babloos-hs/src/06bffa288865af1fc3...
What are my chances of getting a remote Haskell job? Based on my experience, What kind of position/pay can I expect to get? If it matters, I have 9 years of professional experience in developing web applications.
It's petty, but the visual appearance of your code will affect how a client evaluates your knowledge of Haskell. You could use stylish-haskell and hindent to help clean things up.
We use a (tiny) Pipes project to tech screen candidates because it's usually stuff like Pipes/Conduit that'll make somebody thrash - not straight-forward stuff in IO. Ability to overcome or preexisting experience is what we're looking for. That doesn't necessarily apply to other companies hiring/contracting with Haskellers of course.
I don't know what your focus is, but Haskell isn't _too_ much different from other PLs in that a lot of the work is web, so I'd recommend getting comfortable in Yesod & Snap. Nothing wrong with Spock/Scotty, but a lot of apps will be written in them. There is more numerical work to be had, but if you just want to get work that is Haskell, whatever that may be, that's where you'll want to get comfy.
https://bitbucket.org/sras/babloos-hs/src/06bffa288865af1fc3... looks Maybe Monad'ish.
https://bitbucket.org/sras/babloos-hs/src/06bffa288865af1fc3... you can kill duplication like this by having a getOne variant of your query function. Many db clients provide this.
https://bitbucket.org/sras/babloos-hs/src/06bffa288865af1fc3... this is what I mean by petty cleanup.
https://bitbucket.org/sras/babloos-hs/src/06bffa288865af1fc3... Use a quasiquoter so you can write the query naturally, but inline in the code.
https://bitbucket.org/sras/babloos-hs/src/06bffa288865af1fc3... this whole datatype is kinda suspect and you need newtypes for the Text soup. See Bloodhound below for how I use newtypes to make the types more useful.
If you'd like example code, my Elasticsearch client is pretty reasonable I think: https://github.com/bitemyapp/bloodhound
The projects you've linked look non-trivial and pretty cool, just needs refinement that comes from experience and improvement. Beyond that, seek out sticking points that trip up beginning/intermediate Haskell programmers like getting comfortable in mature web frameworks, with monad transformers, and with a streaming library or two. (Pipes and Conduit)
I'd recommend contributing to an existing library as well, if you have the time.
I can't guarantee you'll find a Haskell gig if you do all this. If you're serious about finding one, your best bets are consulting with one that already uses Haskell (start keeping a spreadsheet of companies using Haskell and whom you've contacted) or join an early stage company open to transition. I don't recommend the latter until you are _very_ comfortable in Haskell and can teach it.
I hope this helped, good luck! Please ping me at my email (it's on my github) if you have further questions but keep in mind the book has me very busy so I might be slow to reply.
Bought your book a while ago -- didn't realize your background was in ad tech. I personally eeked out ~30ms avg latencies with PHP by modifying ReactPHP (non-blocking event loop) and getting creative with Redis and nginx.
It's performing comparably at high loads, and saving us a ton of money but obviously... it's PHP. I'm obsessed with Haskell and have spent a bit of time playing around with HLearn as I think SVM is an obvious use case where it would excel (though in terseness/structure more than performance relative to C).
Any advice on selling a rewrite and/or challenges as one nears 1B daily queries for which Haskell would be uniquely advantageous?
I want to integrate Haskell in our high-frequency serving arm (or as a backup, for machine learning), but honestly don't know enough about it relative to performance to sell its advantages over Erlang, Go or even C. Aside from the obvious "functional is better" and "types are great," which tend to appeal to the folks who see beauty in the language, is it feasible to claim that Haskell will result in a faster, more performance program in less time? ... And not to wear out my welcome, but I'm interested in reading more about performant Haskell (as per your post); any resources you recommend?
hand wobble
This is my second ad-tech gig but I'm more of a catch-all SSE that is into databases/dist-sys and who usually works at startups.
>I personally eeked out ~30ms avg latencies with PHP by modifying ReactPHP
That's quite good. We haven't done any real tuning or cleanup with our application, I think if we did we could get a fair bit lower. The fact is, our app is fast because we avoid network I/O like the plague and we don't write _totally_ clownshoes Haskell code. It hasn't taken much work otherwise. We even have unnecessary String/List values, inefficient parsers, and some other things rolling around. Haven't had time for a clean sweep (feature push rn).
>Any advice on selling a rewrite and/or challenges as one nears 1B daily queries for which Haskell would be uniquely advantageous?
Haskell's just really good at anything enterprise really. Frontend JS, API, traditional web backend. As long as you avoid weird/unpleasant libraries and you're proficient/comfortable in Haskell it can be a great experience. Libraries in Haskell are generally pretty opinionated so you need to make sure you know what you like. HLearn is a good example of that opinionatedness being very fecund but also unusual to people accustomed to more ordinary ML libraries. HLearn is super cool :)
Haskell's a bit less compelling for non-JS apps because you'll be serving a volleyball over the FFI net (iOS, Android) but I know people that have done both fruitfully and happily. Haskell for Mac (http://haskellformac.com/) is a Swift+Haskell app from what I understand from Chakravarty. Manuel Chakravarty's done some really cool work on enabling programmers to avoid FFI (not that it's bad, the FFI is really quite nice actually) through the use of the inline objective c support.
> honestly don't know enough about it relative to performance to sell its advantages over Erlang, Go or even C.
If you have the labor, time, and money to write something in C, I want to work where you work.
So that I can write it in Haskell and use the spare time to make it extra shiny.
Okay I'll take the Erlang/Go bits seriously:
Erlang: if you're perf sensitive, shared memory is reaaaaallyyyy nice. The concurrency primitives in Haskell give you database-style transactions which COMPOSE, you can't do that in Erlang. Re: shared memory - look at how much of CouchDB was C. Haskell has a superset of the options Erlang offers. It doesn't excel as deeply in the niche Erlang offers but I think it does a credible job.
Worth considering re: Erlang:
1. http://hackage.haskell.org/package/courier
2. http://haskell-distributed.github.io/
Go: Okay, Go has shared memory but Haskell again has a superset of options. The most common concurrency primitives in Haskell are the TVar (I default to this one for correctness) and the MVar. The Tvar is your STM container - that's where your transactions come from. MVar is a bit like a Golang channel in that it blocks by default unlike Erlang's messaging.
However, we can get away with defaulting to having blocking APIs because our threading is _cheaper_ than Erlang's! Green threads + shared memory -> fuck it, fire off a thread. It's also nice because it means we can preserve our usual synchronous intuition within the context of a single thread and block when we really do want to block.
We actually have an even smaller and cheaper execution context called a spark, but that's more concerned with parallelism than concurrency so we'll put it aside for the moment.
If you want a rundown on strengths & weak points, I suggest looking at the State of the Haskell ecosystem document Gabriel Gonzalez put together - it's very good!
https://github.com/Gabriel439/post-rfc/blob/master/sotu.md
Re: what I've just said, particularly note the points about concurrency:
https://github.com/Gabriel439/post-rfc/blob/master/sotu.md#s...
But all the bullshit aside, I write Haskell because it's really fun (I have no patience for things computers can do) and keeps me from going feral and quitting society.
Btw, the title says "when", not "before", big difference.
https://new-hn.algolia.com/?experimental&sort=byDate&prefix&...
do { a <- f ; m } ≡ f >>= \a -> do { m }
Sometimes i think the authors of Haskell had a completely different glyph set. I'm sure some of you folks can read this like a book, but this sort of thing still throws me into the ditch: M2 :: (Monad m) => (a1 -> a2 -> r) -> m a1 -> m a2 -> m rIt's in no way comparable to learning math notation or music notation.
There's a few things, like fixity declarations, that make parsing Haskell not quite as easy as it could be.
Implementing exactly Haskell's rule for indentation is incredibly hard. In fact, no known Haskell compiler gets it right.
https://mail.haskell.org/pipermail/haskell-cafe/2009-April/0...
For the record, I can read both standard notation music and guitar tabs. I'm fairly competent at Haskell have written APL/J/K derivative languages for a living. "Mastering reading Haskell" certainly has to do with a lack of "go to this page on the wiki/hoogle/haddock and you can read the meaning and idiomatic usage of the 95% most common operators", not a lack of conceptual grasp of anything difficult.
def M2<T1, T2, Result, Action : Monad>(
f: (T1, T2) => Result,
a: Action<T1>,
b: Action<T2>): Action<Result>
{
…
}
The Haskell notation is a simple, concise representation of the actual semantics. If you try to transpose those semantics to a more conventional notation intended for different semantics, it gets complicated due to the mismatch.This notation I’ve suggested doesn’t even accurately express such details as currying and typeclasses, which the original notation conveys neatly.
1. It's way longer
2. Naming the monad "Action" may be misleading depending
on choice of Monad
3. It's tied syntactically to the syntax of the implementation
4. The <>s are superfluous
5. The argument names are superfluous (to the type)
6. The explicit type quantification is superfluous and verboseBecause of the much longer type names which have nothing to do with the syntax.
By just replacing all the type names by the original single-letter names, it goes from 107 to 72 characters, that's almost 50% increase in size (49%) due to longer type names, which incidentally is nearly double the increase in size between the Haskell signature and this new signature (57 -> 72, or 26%)
def M2<A1, A2, R, M: Monad>(f: (A1, A2) => R, a: M<A1>, b: M<A2>): M<R>
Funnily enough, this expansion is relatively worse if applied to the Haskell signature (57 -> 87, +53%)> Naming the monad "Action" may be misleading depending on choice of Monad
Nothing to do with syntax either.
> It's tied syntactically to the syntax of the implementation
?
> The argument names are superfluous (to the type)
That can trivially be removed when just displaying the function's type (`f`'s arguments are not explicitly named after all), likewise for the unnecessary type quantifications, so a type-view of this function (rather than a source view) could be:
def M2<M: Monad>((A1, A2) => R, M<A1>, M<A2>) => M<R>
Oh look, it's now shorter than the original signature (though somewhat more noisy, I'll give you that) def M2 ((A1 => A2 => R) => M<A1> => M<A2> => M<R>) where Monad<M>
At which point the Haskell syntax is shorter. However, concision is not the goal—it’s merely a proxy for clarity.But the conventions are whatever we want for a resyntacting no?
> However, concision is not the goal
I was answering a comment putting concision forward as reasons to dislike the alternate syntax.
Clearly, as you demonstrate, the syntax described can be shortened—but it wasn't.
And by the definition being tied to the implementation I mean that it's not possible to write, e.g.
foo :: Type1
bar :: Type2
foo = ...
bar = ...pekk asked what was wrong with the syntax.
> Clearly, as you demonstrate, the syntax described can be shortened—but it wasn't.
I didn't change the syntax in any way.
> And by the definition being tied to the implementation I mean that it's not possible to write, e.g.
'k.
Is that actually used? Global type inference I could see, but that seems odder.
It is used quite frequently, actually, e.g. in describing type classes.
Well, its an ML-derived syntax rather than the more common Algol-derived syntax, which forms something of a familiarity obstacle for programmers whose background is largely in Algol-derived syntaxes.
Its probably somewhat easier if you've learned another ML family language first -- especially given that idiomatic Haskell often involves wrapping your head around semantics that are unfamiliar as well, so that you aren't trying to take on both at once.
OTOH, I think the kind of ML-based syntax Haskell uses is a good fit, and its worth learning.
Now, I'm not at that level of proficiency, but I can tell that your example above takes a binary function and 'lifts' it into a higher (monadic) context, so that it can work on monadic values. And the reason I can work this out is because I know about the Functor typeclass which has the function fmap :: (a -> b) -> f a -> f b, which does a similar job of lifting an input function into a higher context (in this case a functor context).
Your example of course is `do` notation which is tricky, and is really kind of advanced -- it's much better to use `>>=` `>>` and `return` as long as you can stand before breaking out `do`. If nothing else avoiding `do` will make you write smaller functions!
By "syntax" of course I mean "keywords". Haskell allows for infix operator creativity which you might see as a drawback. I would rather have a language be maximally expressive, allow me to define my own infix swear-word "operators", than be coddled and censored like almost every other language outside C++. But as for built-in syntax, there's hardly any. `$` and `.` are freaking library functions.
As for types, well, types are hard to read. Why shouldn't they be? It's not like templates are easy in C++ once you leave the most trivial exercises. At least Haskell's are formally consistent and fundamentally related to your definitions -- unlike templates which are really macro soup.
In any case, I would find your `M2` definition a lot easier to read with `a` and `b` instead of `a1` and `a2` ...
That's why people favor Haskell in enterprise environments because types keep everything sane when you have multiple people working on the same project or you have large third-party dependency trees.
This article discussed getting everything working and then doing a cabal freeze which is something I hadn't seen before. I would like to be able to simply update a project to new library versions but maybe I should drop that desire. Any advice?
Nix has a lot of haskell packages in it already, and guix has a hackage 'import' tool which helps you with writing a new package definition for a project that hasn't already been packaged.
You may think that's overkill, and fair enough. But I've been through cabal hell before, and I'd honestly rather not anymore, ever.
[1]: https://ivanmiljenovic.wordpress.com/2010/03/15/repeat-after...
Haskell is an enterprise language, not an application language. The benefits of Haskell have played out better there.
Check out Servant while you're at it, it's awesome.
If you don't like writing the client-end in Javascript, you can use Haste or GHCJS and do it in Haskell.
WAI already supports HTTP/2.0 (before Apache has got around to it).