Lately I've been playing around with Haskell, and specifically the whole functors concept. The more I dive into it, the more a-ha moments I'm getting, and it certainly tickles my dopamine receptors quite a bit.
The problem I'm stuck with is the following. Here's the code that works, it lifts the function and then applies it first to the IO value, and then to a List.
replicator1 =
fmap (replicate 3)
replicator2 =
fmap (replicate 3)
main = do
replicated <- replicator1 getLine
print (replicator2 replicated)
It is very tempting to write it in a more concise manner, i.e.:
replicator =
fmap (replicate 3)
main = do
replicated <- replicator getLine
print (replicator replicated)
Part of me says it's conceptually right, since replicator
should be applyable both to IO and to List instances, but being a strongly typed language Haskell doesn't allow me to do so. I think I pretty much understand why is this happening.
The question is: is there any way I can get any closer to the latter variant? Or is it fine to live with the former?
Thank you!
Your code is actually fine, except you ran into the dreaded monomorphism restriction which kept Haskell from inferring the most general possible type for replicator
.
Essentially, with the restriction on, Haskell will not infer polymorphic types for bindings that do not look like functions. This means it has to pick a concrete functor like []
or IO
for replicate
and causes an error if you try to use it in two different contexts.
You can make your code work in three ways:
turn off the monomorphism restriction: add {-# LANGUAGE NoMonomorphismRestriction #-}
at the top of your module.
make replicator
look like a function:
replicator x = fmap (replicate 3) x
add explicit type signatures to your code
replicator :: Functor f => f a -> f [a]
replicator = fmap (replicate 3)
The third option is the most idiomatic. Good Haskell style involves adding explicit type signatures to all of your top-level identifiers. However, it's useful to know the other two options to understand what's going on and to be able to write quick-and-dirty throwaway scripts in Haskell without worrying about type signatures.