jll
jll

Reputation: 815

defining haskell functions that have Maybe in signature

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

Answers (2)

Jon Purdy
Jon Purdy

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

luqui
luqui

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

Related Questions