简体   繁体   中英

Understanding signature of data type, typeclass, and making a data type an instance of a typeclass

Been reading Learn You A Haskell For a Great Good ! and have big trouble with understanding instance and kind.

Q1: So the type t in Tofu t acts as a function with the kind signature (* -> (* -> *)) -> * ? And the overall kind signature of tofu is * -> * , isnt it? since (* -> *) -> * results in * and so does (* -> (* -> *)) -> *

Q2: When we want to make Frank ab instance of the typeclass Tofu t , data type Frank ab must also have the same kind with t . That means kind of a is * , b is * -> * , and ba which will be (* -> *) -> * which results in * . Is that correct?

Q3: The x in tofu x represents ja since both have the kind of * . Frank with its kind (* -> (* -> *)) -> * is applied on x . But I'm not sure how presenting ja as x will distinguish the x in tofu x which is ja and the x in Frank x which is aj .

I'm kind of new to the idea of having a function inside data type or class (Ex: b in Frank ab or t in Tofu t ) which is a bit confusing

I leave the link here since quoting would make the post look unnecessarily long. link

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

data Frank a b = Frank {frankField :: b a} 

instance Tofu Frank where
  tofu x = Frank x 

Q1:

So the type t in Tofu t acts as a function with the kind signature (* -> (* -> *)) -> * ?

t 's kind is * -> (* -> *) -> * , or more explicitly * -> ((* -> *) -> *) , not (* -> (* -> *)) -> * .

And the overall kind signature of tofu is * -> *, isnt it?

tofu doesn't have a kind signature, only type constructors do; its type's kind is * . So are its argument's and result's types. And same for any function.

Q2: You start with a wrong supposition: instance Tofu Frank makes the Frank type constructor an instance of Tofu , not Frank ab . So it's Frank which must have the same kind as t , not Frank ab (which has kind * ).

ba which will be (* -> *) -> *

No, ba is an application of b of kind * -> * to a of kind * , so the application has kind * . Exactly as if b was a function of type x -> y , and a was a value of type x , ba would have type y , not (x -> y) -> x : just replace x and y by * .

Q3:

The x in tofu x represents ja

"Has type", not "represents".

since both have the kind of *

x doesn't have a kind, because it isn't a type.

Frank with its kind (* -> (* -> *)) -> * is applied on x

No, in

tofu x = Frank x

it's the Frank data constructor which is applied to x , not the type constructor. It's a function with signature b a1 -> Frank a1 b (renaming a so you don't confuse it with tofu 's). So b ~ j and a1 ~ a .

Alexey already had a go at answering your questions. I'll instead expound on your example with whatever details seem relevant.

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

The highlighted bits must have kind * . Anything on either side of a (type level) arrow must have type * [1] , and the arrow term itself (that is, the whole ja -> taj term) also has kind * . Indeed, any "type" [2] that can be inhabited by a value has kind * . If it has any other kind, there can't be any values of it (it is just used as to construct proper types elsewhere).

So, within the signature of tofu , the following holds

j a :: *
t a j :: *

because they are used as "inhabited" types, since they are arguments to (->) .

And these are the only things constraining the class. In particular, a can be any kind. With PolyKinds [3]

a :: k   -- for any kind k
j :: k -> *
t :: k     ->   (k -> *) -> *
     ^          ^^^^^^^^    ^
 kind of a      kind of j   required since is used as inhabited type by ->

So we found the required kind of t .

We can use a similar reasoning for Frank .

data Frank a b = Frank {frankField :: b a}
     ^^^^^^^^^                        ^^^

Again the highlighted bits have to have kind * , because they can have values. Otherwise there are no constraints. Generalizing, we have

a :: k
b :: k -> *
Frank a b :: *

And thus

Frank :: k -> (k -> *) -> *

We can see that Frank 's kind matches the required kind for Tofu . But it also makes sense for a more specific kind, for example:

data KatyPerry a b = KatyPerry a (b Int)

Try to deduce her kind, and check that it is more specific than the kind required by Tofu .


[1] This is even true of arrows at the kind level if we assume TypeInType . Without TypeInType , the "kinds of kinds" are called sorts and nobody worries about them; there's usually nothing interesting happening at that level.

[2] I put "type" in quotes because technically only things with kind * are called types, everything else is called a type constructor . I tried to be precise about this but I couldn't find a non-awkward way to refer to both at once and the paragraph got very messy. So "type" it is.

[3] Without PolyKinds , anything with an unconstrained kind like k gets specialized to * . It also means that Tofu 's kind could depend on what type you first happen to instantiate it at, or whether you instantiate it at a type in the same module or a different module. It's bad. PolyKinds is good.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM