2

I can compose pure functions:

let f x = x + 1
let g x = x + 2
let z = f . g
z 1 == 4

I seem to be able to compose monadic functions also:

let f x = Just (x + 1)
let g x = Just (x + 2)
let z x = f x >>= g
z 1 == Just 4

I think I should be able to treat f and g from the last example as applicatives and compose those also, just not sure how:

let f x = Just (x + 1)
let g x = Just (x + 2)
let z x = f <*> g -- this doesn't work
z 1 == Just 4

Is this doable?

Bonus points, can z x = f x >>= g be written as a point-free function? Something like z = f >>= g?

Benjamin Hodgson
  • 42,952
  • 15
  • 108
  • 157
zoran119
  • 10,657
  • 12
  • 46
  • 88

3 Answers3

6
{-# LANGUAGE TypeOperators #-}

The (type-level) composition of any two applicative functors,

newtype (f :. g) a = Compose { getCompose :: f (g a)) }

is an applicative functor.

instance (Functor f, Functor g) => Functor (f :. g) where
    fmap f = Compose . fmap (fmap f) . getCompose

instance (Applicative f, Applicative g) => Applicative (f :. g) where
    pure = Compose . pure . pure
    Compose fgf <*> Compose fgx = Compose ((<*>) <$> fgf <*> fgx)

Your example is the composition of the Maybe applicative with the "function" or "reader" applicative (->) r.

type ReaderWithMaybe r = ((->) r) :. Maybe

x, y :: ReaderWithMaybe Int Int
x = Compose $ \x -> Just (x + 1)
y = Compose $ \x -> Just (x + 2)

Since ReaderWithMaybe r is an Applicative you can do all the usual Applicative stuff. Here I'm smashing my two values together with +.

ghci> let z = (+) <$> x <*> y
ghci> getCompose z 3
Just 9  -- (3 + 1) + (3 + 2) == 9

Note that x and y both get the same input 3. That's the behaviour of (->) r's Applicative instance. If you want to take the result of f x = Just (x + 1) and feed it into g x = Just (x + 2) (to get something equivalent to h x = Just (x + 3)), well, that's what Monad is for.


Bonus points, can z x = f x >>= g be written as a point-free function? Something like z = f >>= g?

You can easily define Kleisli composition by hand.

(>=>) :: Monad m => (a -> m b) -> (b -> m c) -> a -> m c
f >=> g = \x -> f x >>= g

It happens that >=> already exists in the standard library, along with its sister <=<. They are lovingly known as the "fish" operators, and they live in Control.Monad.

Community
  • 1
  • 1
Benjamin Hodgson
  • 42,952
  • 15
  • 108
  • 157
  • I'm struggling to read this :( - `Compose` and `ReaderWithMaybe` are confusing me. Despite this, the `let z = (+) <$> x <*> y` is interesting because you specify an operator `(+)` to use to compose these two functions together. This is not needed for `f . g` or `f >=> g` which I find a bit odd... – zoran119 Apr 25 '16 at 12:23
  • 1
    A trick I use when I'm confused by a `newtype` is simply expanding the definition by hand. You'll see that `ReaderWithMaybe r a` is isomorphic to `r -> Maybe a`. – Benjamin Hodgson Apr 25 '16 at 12:26
  • And, yes, I'm mapping `+` over my two applicative values. Look at the type: `ReaderWithMaybe Int Int` is a _value_ of type `Int` in the _context_ `ReaderWithMaybe Int`. You can't apply `.` or `>=>` to a pair of `Int`s. If I had, say, `f :: ReaderWithMaybe Int (a -> b)` and `g :: ReaderWithMaybe Int (b -> c)` then I could write `(.) <$> g <*> f` – Benjamin Hodgson Apr 25 '16 at 12:30
3

Applicative functions aren't

let f x = Just $ x + 1
let g x = Just $ x + 2

, they're

let f = Just $ \x -> x + 1
let g = Just $ \x -> x + 2

. Composition then works like liftA2 (.) f g or (.) <$> f <*> g.

Gurkenglas
  • 2,317
  • 9
  • 17
  • If I use `let f` and `let g` from your answer, then `let z = (.) <$> f <*> g`, I get `Couldn't match expected type ‘Integer -> t’ with actual type ‘Maybe (Integer -> Integer)’`. Am I doing something wrong here? – zoran119 Apr 25 '16 at 12:13
  • you might need to add an additional type signature (or use named functions with type signatures) – epsilonhalbe Apr 25 '16 at 12:14
  • @epsilonhalbe @Gurkenglas, `f,g :: Int -> Maybe (Int -> Int)`, right? What should be the type for of `z = (.) <$> f <*> g`? – zoran119 Apr 25 '16 at 12:36
  • no `f,g :: Maybe (Int -> Int)` => `z :: Maybe (Int -> Int)` – epsilonhalbe Apr 25 '16 at 12:38
  • Applicative does not work on contained values but only on containers, see what happens if you use `h :: Maybe (Int -> Int); h = Nothing` – epsilonhalbe Apr 25 '16 at 12:39
0

Maybe you will be interested by Kleisli composition of monads:

(<=<) :: Monad m => (b -> m c) -> (a -> m b) -> a -> m c
(>=>) :: Monad m => (a -> m b) -> (b -> m c) -> a -> m c