exampleFunc
fails to compile because the Lens
type synonym is polymorphic and occurs in the signature in what is called "negative position", that is, to the left of the ->
.
You can use Lens
in a type signature even without having RankNTypes
on. This typechecks:
import Control.Lens
lensy :: Lens' (a,b) a
lensy = _1
But this fails to typecheck:
oops :: Lens' (a,b) a -> Int
oops = const 5
Why? For the same reason this also fails to typecheck without RankNTypes
:
{-# LANGUAGE ExplicitForAll #-}
fails :: (forall a. a -> Int) -> Int
fails = undefined
Here the forall
is in negative position, and ranges only over the a -> Int
. It is the implementation of fails
, and not the caller of fails
, the one who chooses the type of a
. The caller must supply an argument function that works for all a
. This feature requires the RankNTypes extension.
When the forall
ranges over the whole signature (as when Lens
is defined in isolation) there's no need for RankNTypes
. This typechecks:
{-# LANGUAGE ExplicitForAll #-}
typechecks :: forall a. (a -> Int) -> Int
typechecks = undefined
But this function is different from the previous one, because here it is the caller who chooses the type of a
. He can pass an argument function which works only for a particular a
.
exampleFunc'
worked because, when no forall
is specified, there are implicit foralls
for each variable, ranging over the whole signature.
This explanation from the Haskell mailing list may be useful.