2

When I don't grasp how an expression in Haskell works I often find it helps to decompose it into a more basic form.

Using the following definitions

sequenceA :: (Applicative f) => [f a] -> f [a]
sequenceA [] = pure []
sequenceA (x:xs) = (:) <$> x <*> sequenceA xs

instance Applicative ((->) r) where
    pure x = (\_ -> x)
    f <*> g = \x -> f x (g x)

I rewrote sequenceA [(+3),(+2)] 3 as

(\_ -> (:)) <*> (+3) <*> ((\_ -> (:)) <*> (+2) <*> (\_-> [])) $ 3

And then turned it into (please excuse the format; I'm not sure what the convention is for splitting lines)

(\d ->(\c->(\b -> (\a -> (\_ -> (:)) a (+3) a) b (\_ -> (:)) b) c (+2) c) d (\_ -> []) d) 3

This seems right when I work through it by hand, but I can't get GHCi to accept it. What have I done wrong here? My second question is how to convert from this form into functional composition. I've tried substituing dots in various combinations, but GHCi rejects all of them....

Will Ness
  • 70,110
  • 9
  • 98
  • 181
planarian
  • 2,047
  • 18
  • 18

3 Answers3

7

Being an idle goodfornothing, I thought I would make a computer do the expansion for me. So into GHCi, I typed

let pu x = "(\\_ -> " ++ x ++ ")"
let f >*< a = "(\\g -> " ++ f ++ " g (" ++ a ++ " g))"

So now I have funny versions of pure and <*> which map strings which look like expressions to string which look like more complicated expressions. I then defined, similarly, the analogue of sequenceA, replacing functions by strings.

let sqa [] = pu "[]" ; sqa (f : fs) = (pu "(:)" >*< f) >*< sqa fs

I was then able to generate the expanded form of the example as follows

putStrLn $ sqa ["(+3)","(+2)"] ++ " 3"

which duly printed

(\g -> (\g -> (\_ -> (:)) g ((+3) g)) g ((\g -> (\g -> (\_ -> (:)) g ((+2) g)) g  ((\_ -> []) g)) g)) 3

This last, copied to the prompt, yielded

[6,5]

Comparing the output from my "metaprogram" with the attempt in the question shows a shorter initial prefix of lambdas, arising from a shallower nesting of <*> operations. Remember, it's

(pure (:) <*> (+3)) <*> ((pure (:) <*> (+2)) <*> pure [])

so the outer (:) should be only three lambdas deep. I suspect the proposed expansion may correspond to a differently bracketed version of the above, perhaps

pure (:) <*> (+3) <*> pure (:) <*> (+2) <*> pure []

Indeed, when I evaluate

putStrLn $ pu "(:)" >*< "(+3)" >*< pu "(:)" >*< "(+2)" >*< pu "[]" ++ " 3 "

I get

(\g -> (\g -> (\g -> (\g -> (\_ -> (:)) g ((+3) g)) g ((\_ -> (:)) g)) g ((+2) g)) g ((\_ -> []) g)) 3

which looks like it matches the (updated)

(\d -> (\c -> (\b -> (\a -> (\_ -> (:)) a ((+3) a)) b ((\_ -> (:)) b)) c ((+2) c)) d ((\_ -> []) d)) 3

I hope this machine-assisted investigation helps to clarify what's going on.

pigworker
  • 43,025
  • 18
  • 121
  • 214
  • Your methodology is over my head, but that's definitely the answer to my first question. Any thoughts about the second? (Translating to (.) notation) – planarian Aug 10 '12 at 18:59
  • 2
    My methodology is just to mechanize the process of "writing out the expansion" by generating the relevant expressions as a string. Where `pure` computes the constant function, `pu` computes the *text* of the constant function. Meanwhile my main thought about translation in terms of constant functions is that it won't be particularly enlightening to do so. Let's start just by expressing `<*>` via composition, and...it's a nuisance because the 3rd (*environment*) argument is copied. We need a duplicator, e.g. `join f a = f a a`, and then `(<*>) = (join .) . ((.) . flip)` which ain't much light. – pigworker Aug 10 '12 at 19:27
  • 4
    In case you think I figured that out by myself, I'd better point you to this handy page about combinatory logic: http://en.wikipedia.org/wiki/B,C,K,W_System The `Applicative` instance for `((->) a)` takes `pure` as the K combinator, `<*>` as the S combinator. Translating applicative-style expressions to composition amounts to translating SK-combinator terms to BCKW-combinator terms, which is doable (both systems are complete) but grungy. – pigworker Aug 10 '12 at 19:31
4

You rewrote (\_ -> (:)) <*> (+3) as \a -> (\_ -> (:)) a (+3) a, which is rewriting f <*> g as f x g x instead of f x (g x). I think you made that mistake for every <*>.

Sjoerd Visscher
  • 11,840
  • 2
  • 47
  • 59
  • You're right, but adding parentheses doesn't fix it. Here's the update: (\d ->(\c->(\b -> (\a -> (\_ -> (:)) a ((+3) a)) b ((\_ -> (:)) b)) c ((+2) c)) d ((\_ -> []) d)) 3 – planarian Aug 10 '12 at 17:49
2

It might be easier to use combinators, e.g. _S and _K, symbolically, and not their definitions as lambda-expressions,

_S f g x = f x (g x)
_K x y   = x

With functions, fmap is (.) and <*> is _S, as others already mentioned. So,

sequenceA [(+3),(+2)] 3 ==
    ( ((:) <$> (+3)) <*> sequenceA [(+2)]        ) 3  ==
    _S ((:).(+3))   ( ((:) <$> (+2)) <*> pure [] ) 3  ==
    _S ((:).(+3))   ( _S ((:).(+2))   (_K [])    ) 3  ==
       ((:).(+3)) 3 ( _S ((:).(+2))   (_K [])  3 )    ==
       ((:).(+3)) 3 (    ((:).(+2)) 3 (_K [] 3)  )    ==
    (6:) ( (5:) [] ) ==
    [6,5]

So it might be easier to decompose expressions down to basic functions and combinators and stop there (i.e. not decomposing them to their lambda expressions), using their "re-write rules" in manipulating the expression to find its more comprehensible form.

If you wanted to, you could now write down for yourself a more abstract, informal re-write rule for sequenceA as

sequenceA [f,g,..., z] ==
    _S ((:).f) . _S ((:).g) . _S ..... . _S ((:).z) . _K []

and so

sequenceA [f,g,..., z] a ==
    ((:).f) a $ ((:).g) a $  ..... $ ((:).z) a $ _K [] a ==
    (f a:)    $ (g a:)    $  ..... $ (z a:)    $ []      ==
    [f a, g a, ..., z a]

and hence

sequenceA fs a == map ($ a) fs == flip (map . flip ($)) fs a

to wit,

Prelude Control.Applicative> flip (map . flip ($)) [(+3),(+2)] 3
[6,5]
Will Ness
  • 70,110
  • 9
  • 98
  • 181