Reputation: 1408
My code is the following
data Tree a = Leaf a | Node (Tree a) (Tree a)
deriving (Show)
f x = x + 1
myTree = Node (Node (Leaf 2) (Leaf 3)) (Leaf 4)
maptree f (Leaf a)= Leaf (f a)
maptree f (Node xl xr ) = Node (maptree f xl) (maptree f xr)
which can add one to the Leaf element in the tree.
I further look at the function type which are
*Main> let maptree f (Leaf a)= Leaf (f a)
maptree :: (t -> a) -> Tree t -> Tree a
*Main> let maptree f (Node xl xr ) = Node (maptree f xl) (maptree f xr)
maptree :: t -> Tree t1 -> Tree a
What do t, a, and t1 mean over here? Is there any reference that I can read for those stuffs?
Thanks.
Upvotes: 1
Views: 293
Reputation: 1223
We'll start with the first signature:
(t -> a) -> Tree t -> Tree a
This essentially says, "Given a function that takes something of type t
and produces an a
and also a Tree
containing items of type t
, produce a Tree
containing elements of type a
.
t
and a
are completely arbitrary names that GHCi generated; we could have easily said:
(originalType -> newType) -> Tree originalType -> Tree newType
Although most programmers would write:
(a -> b) -> Tree a -> Tree b
Now, the second signature:
t -> Tree t1 -> Tree a
This signature's strange because, like Hammar pointed out, the two maptree
definitions you wrote in GHCi are completely independent. So let's look at the second definition all by itself:
maptree f (Node xl xr) = Node (maptree f xl) (maptree f xr)
Here, the type of f
does not matter as you don't apply it to any arguments and you only pass it to maptree
, which recursively doesn't care what type f
is. So let's give f
type t
, as it doesn't matter.
Now, similarly, there's no constraint on xl
and xr
, as they are only passed to maptree
which doesn't know their types except that it should be a Tree
. Thus, we might as well call their type Tree t1
.
We know this function will return a Tree
because of the Node
constructor, but the previous two types we looked at have no bearing on the type of elements in the tree, so we may as well call it Tree a
.
As an example, let's see what happens when we expand the following:
maptree True (Node (Leaf 0) (Leaf 1))
= Node (maptree True (Leaf 0)) (maptree True (Leaf 1))
Which then fails because there's no way for maptree
to expand a Leaf
in this case.
However, the types work out:
True :: t
Node (Leaf 0) (Leaf 1) :: Tree t1
0 :: t1
1 :: t1
So that signature was very odd, but it does make sense. Remember to not overwrite definitions, I guess.
Upvotes: 2
Reputation: 139910
Identifiers beginning with a lower case letter, like t
, a
and t1
, are type variables when used in a type context. They are placeholders which can be specialized to any type. See this answer for more information on how to read type signatures.
Also note that in GHCi, your example will first define one function which only handles the Leaf
case, and then replace it with another which only handles the Node
case, unlike in Haskell source files where they will be two cases of the same function. To specify multiple equations for a function in GHCi, separate them with a semicolon:
*Main> let maptree f (Leaf a) = Leaf (f a); maptree f (Node xl xr) = Node (maptree f xl) (maptree f xr)
or use multiline mode:
*Main> :{
*Main| let maptree f (Leaf a) = Leaf (f a)
*Main| maptree f (Node xl xr) = Node (maptree f xl) (maptree f xr)
*Main| :}
Upvotes: 3