Reputation: 815
i'm unclear about how to write function signatures in haskell, especially using Maybe
. consider:
f :: Maybe a -> Maybe a
f = \a -> a
main = print (f (Just 5))
this works but why can't the function signature just be this?
f :: Maybe -> Maybe
since f
just takes a Maybe
type and returns a Maybe
type.
related: if i wanted to have the Maybe type be more specific and be a Maybe Int
, why doesn't this work?
f :: Maybe Int a -> Maybe Int a
f = \a -> a
main = print (f (Just (Int 5)))
(i'm running all code using runhaskell test.hs
)
Upvotes: 1
Views: 707
Reputation: 54971
Maybe
is a type constructor, essentially a type-level function. It takes a type (such as Int
) and returns a type (such as Maybe Int
). The “types” of types are called kinds: the kind of a type that has values, like Int
, is called *
. The kind of a type constructor that takes one argument is * -> *
. You can see this in GHCi with the :kind
/:k
command:
> :k Int
Int :: *
> :k Maybe
Maybe :: * -> *
> :k Either
Either :: * -> * -> *
In a signature like Maybe a -> Maybe a
, a
is a type variable that gets replaced with a particular type when you call the function. (Implicitly, this means forall a. Maybe a -> Maybe a
, which you can write yourself if you enable extensions such as ExplicitForall
or ScopedTypeVariables
.)
So if you call f :: Maybe a -> Maybe a
on a Maybe Int
, then f
has the type Maybe Int -> Maybe Int
at that call site, because a
has been instantiated to Int
.
The compiler rejects Maybe Int a
because you’re supplying two parameters to Maybe
when it only accepts one. (The a
is not a name for the argument, but a parameter of the type.) Likewise, it rejects Maybe -> Maybe
because you’ve given Maybe
no arguments, so you’re trying to pass two types of kind * -> *
to the function arrow constructor (->)
, which takes arguments of kind *
:
> :k (->)
(->) :: * -> * -> *
As an aside, it is possible to write something like Maybe -> Maybe
and have it expand to Maybe a -> Maybe a
, and this can be useful sometimes, but it’s almost certainly not what you’re intending to do right now.
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE TypeOperators #-}
type (~>) f g = forall a. f a -> g a
f :: Maybe ~> Maybe
f x = x
Here, the type synonym Maybe ~> Maybe
expands to forall a. Maybe a -> Maybe a
, which can be abbreviated to Maybe a -> Maybe a
, the signature you wrote before.
Upvotes: 5
Reputation: 60463
Seems like you are confused about type variables. First of all, in
f :: Maybe a -> Maybe a
f = \a -> a
the a
's in the first line have nothing to do with the a
's in the second line, we could have written:
f :: Maybe a -> Maybe a
f = \x -> x
or even
f :: Maybe foo -> Maybe foo
f = \bar -> bar
The a
's are variables that stand for types. So f
here is declaring that f
has a whole bunch of types at once:
f :: Maybe Int -> Maybe Int
f :: Maybe String -> Maybe String
f :: Maybe (Maybe Bool) -> Maybe (Maybe Bool)
...
and so on. It is not some "labeling" of the arguments as I suspect you think. The fact that the two a
's are the same means that the argument type has to be the same result type. If we had said f :: Maybe a -> Maybe b
we would get this family:
f :: Maybe Int -> Maybe Bool
f :: Maybe String -> Maybe String
f :: Maybe (Maybe Bool) -> Maybe Int
...
that is, the a
and b
can now stand for different types, but the argument and result still have to be Maybe
.
The reason you can't say
f :: Maybe -> Maybe
is because Maybe
is not a type -- it is a type constructor. If you give it a type, it gives you back a type. So Maybe Int
and Maybe String
are types, and in general Maybe a
is a type as long as a
is a type.
Maybe Int a
(which is parsed (Maybe Int) a
) has no meaning because Maybe Int
is not a type constructor -- it does not accept any more arguments.
Suggested reading: Types and Typeclasses from LYAH.
Upvotes: 10