Not very.
The type system really helps you by ensuring code is self-documenting and by pointing out errors you've made. So if you don't remember something you can trivially look it up. And if you don't look it up, you will get a type error telling you what you forgot, where.
I've been using Haskell for years and I still don't remember what order the arguments to foldl
and foldr
take. (They're different between the two!) Every time I need to use a fold—which is reasonably often—I just pop over to GHCi:
Prelude> :t foldl
foldl :: (a -> b -> a) -> a -> [b] -> a
Aha! The accumulator goes first.
I do a lot of list wrangling¹. But I still don't remember most of the list functions. It's very useful to have an idea of what's possible, but you don't need to remember any details. When I need to do list code, I just pop over to GHCi, load the list module and type:
Prelude Data.List> :browse Data.List
(\\) :: Eq a => [a] -> [a] -> [a]
delete :: Eq a => a -> [a] -> [a]
deleteBy :: (a -> a -> Bool) -> a -> [a] -> [a]
deleteFirstsBy :: (a -> a -> Bool) -> [a] -> [a] -> [a]
... 129 or so functions total
I then search for either the fragment of the name I remember ("hmm, this sounds like a 'span' function") or the rough type I need. It also helps that I run GHCi inside an Emacs buffer, which makes navigating around large amounts of output trivial.
This works really well for most modules I use: type signatures in Haskell are surprisingly expressive.
For harder things, I pull out the good 'ol Hoogle which is a Haskell API search engine that is very intelligent about searching for types. How can I join a bunch of strings with a separator? (This function has a really stupid name!)
The second result is the correct one. It found it even though I entered a type that was too specific with the arguments backwards. It's good! (Seriously though, who calls this "intercalate"?)
In general, the type system means I actually have to remember fewer things than in other languages.
That said, there are still a few rough edges where I need more working memory than average. The most grating one is with poor error messages. Often, what an error message says and the actual error that caused it are very different. A trivial example is with typeclasses:
*Draw> 1 + True
<interactive>:10:3:
No instance for (Num Bool) arising from a use of `+'
Possible fix: add an instance declaration for (Num Bool)
In the expression: 1 + True
In an equation for `it': it = 1 + True
I have to mentally translate "No instance for..." as "Expected Number, got Bool".
In reality, this particular example isn't a real problem, but there are more complicated examples that are.
One of the biggest difficulties I had in being productive in OCaml coming from an extensive Haskell background was that my existing intuition for bad error messages was largely useless. This is the sort of thing where experience really helps and I didn't have it.
Error messages cause me some real frustration every now and then. But, overall, it's less frustration than I get from bugs that would sneak in with a worse type system or the extra noise I would have with a weaker inference system.
footnotes
¹ This might seem odd because in many languages linked lists are not a great data type. However, since Haskell is lazy, list wrangling is often far closer to writing loops than it is working with data structures. Quite a lot of complicated control flow can be nicely expressed using standard list functions, and I take full advantage of this in Haskell.
This is one of the reasons I'm actually a big fan of having laziness by default.