Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I make tuples an instance of this class in Haskell?

I've been reading the book "What I wish I knew when learning Haskell" and I stopped on this example:

class Bifunctor p where
    bimap  :: (a -> b) -> (c -> d) -> p a c -> p b d
    first  :: (a -> b) -> p a c -> p b c
    second :: (b -> c) -> p a b -> p a c

My question is: How can I create an instance of that class? The idea is to call the function as:

λ bimap  (+1) (+2) (8, 9) -- (9, 11)
λ first  (*4) (10, 8) -- (40, 8)
λ second (*2) (3, 5) -- (3, 10)

The closest I came to accomplishing this was:

instance Bifunctor (x, y) where
    bimap func func' (x, y) = (func x, func' y)
    first func (x, y) = (func x, y)
    second func (x, y) = (x, func y)

But it doesn't work, it raises an error:

• Expecting two fewer arguments to ‘(x, y)’
  Expected kind ‘* -> * -> *’, but ‘(x, y)’ has kind ‘*’
• In the first argument of ‘Bifunctor’, namely ‘(x, y)’
  In the instance declaration for ‘Bifunctor (x, y)’
like image 507
NrBanMex Avatar asked Jun 08 '20 08:06

NrBanMex


People also ask

What is an instance of a Haskell type class?

An instance of a class is an individual object which belongs to that class. In Haskell, the class system is (roughly speaking) a way to group similar types. (This is the reason we call them "type classes"). An instance of a class is an individual type which belongs to that class.

How do you get the second element in a tuple Haskell?

2. snd. This tuple function is used to get the second element from the tuple values or group. We can use this function before the tuple and it will return us the second element as the result in Haskell.

What is a tuple in Haskell?

A tuple is a fixed-length coupling of values, written in parentheses with the values separated by commas. One way to use this is to pass all parameters into a function as one value, rather than the curried functions we've seen so far.

What does () mean in Haskell?

() is very often used as the result of something that has no interesting result. For example, an IO action that is supposed to perform some I/O and terminate without producing a result will typically have type IO () .


1 Answers

Good question.

The class applies to the functor type itself, and in your case the functor type is (,). To get the feeling about it, notice the difference here.

:t (,)
(,) :: a -> b -> (a, b)

:t (True,False)
(True,False) :: (Bool, Bool)

It would probably have been more intuitive if you had used a Pair type like :

data Pair a b = Pair a b

Because reading the class definition would have made more obvious the type application of 'p'.

Just like Haskell uses types for values, as illustrated above, it uses types for types (also for compile-time logic), which are named Kinds.

:k Pair
Pair :: * -> * -> *

:k (,)
(,) :: * -> * -> *

:k (Bool,Bool)
(Bool,Bool) :: *

:k Bifunctor 
Bifunctor :: (* -> * -> *) -> Constraint

This last line illustrates that Bifunctor class is designed for types of kind (* -> * -> *), not kind (*) of (a,b), hence the error message you had from GHC.

Your definition was almost right, here is the correct one :

instance Bifunctor (,) where
  bimap func func' (x, y) = (func x, func' y)
  first func (x, y) = (func x, y)
  second func (x, y) = (x, func y)

EDIT : illustration of kinds as suggested by @leftroundabout

like image 79
Paul R Avatar answered Oct 04 '22 19:10

Paul R