haskell - types and typeclasses - kinds

We have so far examine the types and typeclasses and we can make some types part of the typeclasses, however, we might have find out that some typeclasses expect to pass in a concrete type while some require to pass in some type which expect one or more type parameters. then how we determine which type classes requires some concrete types while some do not?

 

So, values like 3, "YEAH" or takeWhile (functions are also values, because we can pass them around and such) each have their own type. Types are little labels that values carry so that we can reason about the values. But types have their own little labels, called kinds. A kind is more or less the type of a type. This may sound a bit weird and confusing.

 

a few type examples.

ghci> :k Int  
Int :: * 
ghci> :k Maybe  
Maybe :: * -> *  

 

then let's examine the typeclass Functor, it is defined as below.

 

class Functor f where   
    fmap :: (a -> b) -> f a -> f b  

f is the type variable used in the typeclass, and because (f a) and f b, we can deduce taht the types that want to be friend with Functor has to be * -> *

 

so what if we are making our own types called Tofu. and it looks like this:

 

class Tofu t where  
    tofu :: j a -> t a j  

 

Let's see the kind of the types. Because j a is used as the type of a value that the tofu function takes as its parameter, j a has to have a kind of *. We assume * for a and so we can infer that j has to have a kind of * -> *. We see that t has to produce a concrete value too and that it takes two types. And knowing that a has a kind of * and j has a kind of * -> *, we infer that t has to have a kind of * -> (* -> *) -> *. So it takes a concrete type (a), a type constructor that takes one concrete type (j) and produces a concrete type.

 

So, let' start to make one with the type of (* -> (* -> *) -> *, and here is one way of doing this. 

 

data Frank a b  = Frank {frankField :: b a} deriving (Show)  

 

How do we know this type has a kind of * -> (* -> *) - > *? Well, fields in ADTs are made to hold values, so they must be of kind *, obviously. We assume * for a, which means that b takes one type parameter and so its kind is * -> *. Now we know the kinds of both a and b and because they're parameters for Frank, we see that Frank has a kind of* -> (* -> *) -> * The first * represents a and the (* -> *) represents b. Let's make some Frank values and check out their types.

 

so a few example can show: 

ghci> :t Frank {frankField = Just "HAHA"}  
Frank {frankField = Just "HAHA"} :: Frank [Char] Maybe  
ghci> :t Frank {frankField = Node 'a' EmptyTree EmptyTree}  
Frank {frankField = Node 'a' EmptyTree EmptyTree} :: Frank Char Tree  
ghci> :t Frank {frankField = "YES"}  
Frank {frankField = "YES"} :: Frank Char [] 

 

the details analysis on the result is left out, let see how we make Frank an instance of Tofu, because we see that tofu takes a j a (so an example type of that form would be Maybe Int) and returns a t a j. So if we replace Frank with j, the result type would be Frank Int Maybe.

 

instance Tofu Frank where  
    tofu x = Frank x  

 because we know that Frank is of type * -> (* -> *) -> *, but the value constructor is * -> *, because it only take one argument to construct the Frank object.  so in the example above, x is actually of type *.. but how does the runtime figures out the type parameter a and b??

so one requirement on the parameter x is that it can be decomposed in such a way 

x: *

while it should be decomposed to be 

  (* -> *) -> * -> *, which maps to frankField : b a

suppose x is Just 'a'

then

   b:: *->*:  Just  -- because Just has type * -> *

   a ::* :  Char --

 

   b a :: * : Just Char

and then it reorganize the types, and we get Frank type of Frank Char Just

 

So far we have examine types which matches the typeclasses requirement, but we may deal with some types which does not have an exact type match, e.g. suppose that we have define the following typeclass, 

 

data Barry t k p = Barry { yabba :: p, dabba :: t k }  

 

   Supose that we want to make it an instance of Functor. 

Functor wants types of kind * -> * but Barry doesn't look like it has that kind. What is the kind of Barry? Well, we see it takes three type parameters, so it's going to besomething -> something -> something -> *. It's safe to say that p is a concrete type and thus has a kind of *. For k, we assume * and so by extension, t has a kind of * -> *. Now let's just replace those kinds with the somethings that we used as placeholders and we see it has a kind of (* -> *) -> * -> * -> *. Let's check that with GHCI.

 

ghci> :k Barry  
Barry :: (* -> *) -> * -> * -> *  

 

we can partially apply the first two types parameters so we are left with * -> *, That means that the start of the instance declaration will be:instance Functor (Barry a b) where. If we look at fmap as if it was made specifically for Barry, it would have a type of fmap :: (a -> b) -> Barry c d a -> Barry c d b, because we just replace the Functor's f with Barry c d. The third type parameter from Barry will have to change and we see that it's conviniently in its own field.

 

 

instance Functor (Barry a b) where  
    fmap f (Barry {yabba = x, dabba = y}) = Barry {yabba = f x, dabba = y}  

 

There we go! We just mapped the f over the first field.

 

You may still wonder how does the "c d" come along in the "Barry c d a" notation. and what does the "c" and "d" in the "c d" notation binds to? well, it is all symbol logics, or mathematical logic (check this http://en.wikipedia.org/wiki/Mathematical_logic, where we first need to have a matched type of (* -> *), and because fmap is of type (* -> *) the correlation happens by the type parameters, a and b, while because the second and the third parameter has to be a *, and the second and third parameter has to associate wih the  typeclasses definition by parameter value (a for the second and b for the third)... we have to reserve a space for a and b.. A common way is to 1. first determine what we put as parameter, 2. determine location of the parameters, 3. with some transformation

  1. because partially applied Barry a b has type (*->*), we make instances for Barry a b.
  2. we have to reserve for the type parameters , in this case, fmap :: (a , b) -> f a -> f b, where a and b is fixed.
  3. we replace f with Barry x y (or you can also call it Barry c d) and we get fmap :: (a , b) ->  barry c d a -> barry c d  b
  4. *optional, you may want to deduce the types of like how c, d related to a and b, and their types. 

 

So remembers that types has little types of its own.

 

so in summary the example code is shown as below.

-- file
--  kinds_and_some_typefoo.hs
-- description: 
--   we'll take a look at formally defining how types are applied to type constructors, just like we took a look at formally defining how values are applied to functions by using type declarations.


-- QUOTE: 
--  (functions are also values, because we can pass them around and such) each have their own type. 
-- 

-- Types are little labels that values carry so that we can reason about the values. But types have their own little labels, called kinds.

--
-- how to check the kinds of a types ??
-- the key is the 
-- :k types o
-- e.g.
-- ghci> :k Int
-- Int :: * 

-- How quaint. What does that mean? A * means that the type is a concrete type. A concrete type is a type that doesn't take any type parameters and values can only have types that are concrete types.

-- 
-- checkig Maybe
-- ghci> :k Maybe  
-- Maybe :: * -> *  

-- 
-- checking Maybe Int
-- ghci> :k Maybe Int  
-- Maybe Int :: *  

-- more 
-- ghci> :k Either  
-- Either :: * -> * -> * 
-- 
-- ghci> :k Either String  
-- Either String :: * -> *  
-- ghci> :k Either String Int  
-- Either String Int :: *  


class Tofu t where  
    tofu :: j a -> t a j  

-- j a has to have a kind of *
--
-- so j has a kind of * -> * 
-- and t should be a type of  * -> (* -> *) -> *
-- So it takes a concrete type (a), a type constructor that takes one concrete type (j) and produces a concrete type. Wow.


data Frank a b  = Frank {frankField :: b a} deriving (Show) 
-- ghci> :t Frank {frankField = Just "HAHA"}  
-- Frank {frankField = Just "HAHA"} :: Frank [Char] Maybe  
-- ghci> :t Frank {frankField = Node 'a' EmptyTree EmptyTree}  
-- Frank {frankField = Node 'a' EmptyTree EmptyTree} :: Frank Char Tree  
-- ghci> :t Frank {frankField = "YES"}  
-- Frank {frankField = "YES"} :: Frank Char []


-- now we making Frank an instance of ToFu is preety simple 
instance Tofu Frank where 
   tofu x = Frank x
-- ghci> tofu (Just 'a') :: Frank Char Maybe  
-- Frank {frankField = Just 'a'}  
-- ghci> tofu ["HELLO"] :: Frank [Char] []  
-- Frank {frankField = ["HELLO"]}  


-- we did flex our type muscle, let's do some type-foo. We have this data type

data Barry t k p = Barry { yabba :: p, dabba :: t k }

-- ghci> :k Barry  
-- Barry :: (* -> *) -> * -> * -> *  

instance Functor (Barry a b) where  
    fmap f (Barry {yabba = x, dabba = y}) = Barry {yabba = f x, dabba = y}  

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值