Skip to content

Instantly share code, notes, and snippets.

@AlexeyRaga
Last active November 20, 2017 23:00
Show Gist options
  • Save AlexeyRaga/be97a184d182c9a5d93087230d97d31f to your computer and use it in GitHub Desktop.
Save AlexeyRaga/be97a184d182c9a5d93087230d97d31f to your computer and use it in GitHub Desktop.

Preposition

In FP we love to compose things.

When we have two functions:

f :: a -> b
g :: b -> c

then we can compose them into one function:

h :: a -> c
h  = g . f -- "dot" is Scala's "compose"

Functor

But what if f returns a "contextful" value?

f :: a -> F b
g :: b -> c

Where F can be anything, like Option or Future or IO, etc.? We can't just use "compose" to get a final result. But we can "map over" F to "transform value within it", so we can have a -> F c:

h :: a -> F c
h a = fmap g (f a) -- f(a).map(g) in Scala

When our context F (Option, Future, IO, List, etc.) allows this "map over" operation we call this context a Functor. fmap must obey some intuitive laws which I'll skip for now, but now we know what Functor is: a "context" that we can "map over" to transform its value inside.

fmap therefore looks like:

fmap :: Functor f => (a -> b) -> f a -> f b

Monad

But what if both f and g return "contextful" values?

f :: a -> M b
b :: b -> M c

How do we compose these? If M is a functor, we can't just fmap g (f x) because it doesn't give us M c. Therefore we need another operation that takes f and g and gives us our final M c.

If such an operation exists for a given context M then we call M a Monad. This operation is usually called bind:

h :: a -> M c
h a = bind g (f a) -- in Scala: f(a).flatMap(g)

In Haskell bind is an operator:

(>>=) :: Monad m => m a -> (a -> M b) -> M b

so we can do:

h :: f a >>= g

So a Monad is a context that allows us to "chain" contextful actions. It enforces sequence naturally (nothing to do with IO specifically) because in order to call the 2nd action (g :: b -> M c) it needs to calculate b first (to pass it into g).

A Monad also defines an operation to "construct" the context (to wrap the pure value into a context). This operation is called return:

return :: Monad m => a -> M a

so, for example:

return 2 :: Maybe Int        -- result: Just 2
return 2 :: [Int]            -- result: [2]
return 2 :: IO Int           -- result: IO 2

Examples

Maybe is a monad, so we can compose:

boss :: Department -> Maybe User
userEmail :: User -> Maybe Email

departmentEmail :: Department -> Maybe Email
departmentEmail dep = boss dep >>= userEmail

Future is a monad, so we can compose:

def createEC2Instance(ec2: EC2Params): Future[EC2] = ???
def startEC2Instance(ec2: EC2): Future[EC2] = ???

def upEC2(ec2: EC2Params): Future[EC2] =
  createEC2Instance(ec2).flatMap(startEC2Instance)

We can briefly talk about monad laws now, which are mostly intuitive and trivial, like "if I don't do anything with the value, then nothing changes", etc.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment