I have been slowly working through some more Haskell with help from Tony.
- Quickly worked through the previous stuff we did to refresh my memory.
- Consider a simplified version of the Eq type class.
class Eq a where
The type variable
(==) :: a -> a -> Boola
is polymorphic over types. Now consider the Functor type class.class Functor f where
Here we have an example of higher-order polymorphism, e.g.
fmap :: (a -> b) -> f a -> f bf a
,f b
. The type variablesa
andb
are polymorphic over types as before, butf
is polymorphic over type constructors. Note thatf
has kind* -> *
as it is applied to a single type variable in both cases. - The Maybe type constructor has kind
* -> *
. We have effectively already written theFunctor
implementation, i.e. themmap
function in the previous work.class Functor' f where
(<$>) :: (a -> b) -> f a -> f b
instance Functor' Maybe where
(<$>) = mmap - Observe that
->
is a type constructor with kind* -> * -> *
. Given two typesa
andb
,a->b
is the type of functions mapping elements of typea
to elements of typeb
.
We can't write aFunctor
instance for the->
type constructor as their kinds are different. However we can partially apply a polymorphic type, lets call itt
which gives((->) t)
and has kind* -> *
. - Exercise: write an instance of
Functor
for functions:instance Functor' ((->) t) where
Looks a little nasty when you first see it. Its a bit easier if you write out the type signature with the type constructor applied.
(<$>) = error "todo"<$> :: (a -> b) -> ((->) t a) -> ((->) t b)
It is more obvious in infix form.<$> :: (a -> b) -> (t -> a) -> (t -> b)
Now it is fairly easy to come up with an implementation. - Exercise: just for fun, write a Monad instance:
class (Functor' m) => Monad' m where
Again writing out the type signatures helps.
flatMap :: (a -> m b) -> m a -> m b
pure :: a -> m a
instance Monad' ((->) t) where
flatMap = error "todo"
pure = error "todo" - Note the laws that all
Functor
instances should satisfy.fmap id == id
The combination of the
fmap (f . g) == fmap f . fmap gFunctor
type class and these laws define a particular concept/pattern. Intuitively function composition and the traditional map function seem like very different things, yet they are both instances of this abstraction. - Exercise: write an implementation for:
(<***>) :: (Functor f, Functor g) => (a -> b) -> g (f a) -> g (f b)
It took me a while, with help, so the answer is at the bottom of the post.
This is all well and good, but what use is it? Here are two examples of using the
<***>
function.Example 1
data Connection = Connection
newtype Conn a = Conn {
conn :: Connection -> a
}
instance Functor Conn where
k `fmap` Conn z = Conn (k . z)
data ResultSet = ResultSet
data Person = Person
f :: ResultSet -> Person
f = undefined
executeQuery :: String -> Conn ResultSet
executeQuery = undefined
k :: String -> Conn Person
k = f <***> executeQuery
Example 2
g :: Maybe [Bool] -> Maybe [String]
g = (<***>) show
Useful references.
- A Gentle Introduction to Haskell: 2 Values, Types, and Other Goodies
- Functional Programming with Overloading and Higher-Order Polymorphism
Answer for 7.
(<***>) :: (Functor f, Functor g) => (a -> b) -> g (f a) -> g (f b)Or in point free form.
f <***> g = (f `fmap`) `fmap` g
(<***>) = fmap . fmap
1 comment:
(<***>) = fmap . fmap
Or for kicks:
(<***>) = fmap `fmap` fmap
Though it doesn't buy you anything ;)
Post a Comment