Trying to understand a Haskell code example from the presentation "Why Do Monads Matter?"

StackOverflow https://stackoverflow.com/questions/22718922

  •  23-06-2023
  •  | 
  •  

Question

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 > > > >
Was it helpful?

Solution

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

OTHER TIPS

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

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
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top