Reputation: 20653
I was watching the presentation : " Why Do Monads Matter? "
I simplified one code example which compiles and runs (please see below) but I still don't understand how it works.
More precisely: I don't understand the composePref function. According to the type definition it should take 2 parameters of Ize type and return one result of Ize type. (Ize means "whatdoyoucallit"/thingy/something in Hungarian.)
But it takes three parameters (f g x), could someone please explain how the composePref function works and what are the types of f, g, x, y and c ?
I have to admit that I am very beginner in Haskell. Maybe I don't understand how currying works in this case ?
module Dependence where
main = putStrLn (f "foo" cfg)
where f = right `composePref` right `composePref` left
cfg = 2
left :: Ize
left s = \i -> (repeatString i "< ") ++ s
right ::Ize
right s = \i -> s ++ (repeatString i " >")
repeatString :: Integer -> String -> String
repeatString i s = if (i <= 0)
then ""
else s ++ repeatString (i - 1) s
type Ize = String -> Integer -> String
composePref :: Ize -> Ize -> Ize
composePref f g x = \c -> let y = (g x) c
in (f y) c
Produces output:
< < foo > > > >
Upvotes: 1
Views: 137
Reputation: 54058
You are right in thinking that it is currying that allows this behavior. If we look at the definition of Ize
, it's just a type synonym for String -> Integer -> String
. If we plug this in to the type signature of composePref
, we'd get
composePref :: (String -> Integer -> String) -> (String -> Integer -> String) -> (String -> Integer -> String)
(I hope you see now why a type alias was used, it greatly shortens the signature). Since ->
in type signatures is right associative, it means that something like
a -> b -> c -> d
Is equivalent to
a -> (b -> (c -> d))
So we can further simplify the signature to be (with some extra type aliases because I don't want to type them all)
type I = Integer
type S = String
composePref :: (S -> I -> S) -> (S -> I -> S) -> S -> I -> S
composePref f g x = \c -> ...
Then f :: (S -> I -> S)
, g :: (S -> I -> S)
, and x :: S
. I included the beginning of that labmda so that I could say that c :: I
. You could actually write this function as:
composePref :: Ize -> Ize -> Ize
composePref f g x c = let y = (g x) c in (f y) c
Which is also equivalent to
composePref f g x c = let y = g x c in f y c
-- (map show) [1, 2, 3] === map show [1, 2, 3]
Or
composePref f g x c = f (g x c) c
Or even
composePref f g = \x c -> f (g x c) c
These are all equivalent definitions of composePref
. I think the last might make it most clear that it's a function that takes two functions and returns a new function of the same type.
To try to make it even more clear, I'll write some illegal syntax with type annotations where you aren't really supposed to use them:
composePref (f :: Ize) (g :: Ize) = h
where
h :: Ize
h (x :: String) (c :: Integer) =
let (y :: String) = (g x) c
in (f y) c
Upvotes: 6
Reputation: 1673
You could use the following alternative definition, if this makes things clearer
composePref :: Ize -> Ize -> Ize
composePref f g = \x -> \c -> let y = (g x) c
in (f y) c
Upvotes: 1
Reputation: 144126
If you expand the type of the last Ize
in your function you get:
composePref :: Ize -> Ize -> (String -> Integer -> String)
composePref f g x = \c -> let y = (g x) c
in (f y) c
which is the same as
composePref :: Ize -> Ize -> String -> Integer -> String
which is also the same as:
composePref :: Ize -> Ize -> String -> (Integer -> String)
which matches your definition of composePref
more closely. Now f
and g
are both Ize
while x
is a String and c
is an Integer
Upvotes: 3