Monads!
Impossible to explain!
… I'm not going to teach you how to use them.
Instead we're going to figure out why they're so hard to learn.
And maybe help you get started.
You need to learn it bit-by-bit
// Java-ish List<String> // Contains many strings Optional<String> // Contains a string or nothing Future<String> // Contains a value from the future
// Scala List[String] // Contains many strings Optional[String] // Contains a string or nothing Future[String] // Contains a value from the future
-- Haskell List String -- Contains many strings Maybe String -- Contains a string or nothing Async String -- Contains a value from the future
// Java-ish Monad<A>
// Scala Monad[A]
-- Haskell Monad a
// Java-ish Integer // Definitely not a Monad List<A> // Could be a Monad Optional<A> // Could be a Monad
// Scala Int // Definitely not a Monad List[A] // Could be a Monad Optional[A] // Could be a Monad
-- Haskell Int -- Definitely not a Monad List a -- Could be a Monad Optional a -- Could be a Monad
pure// Java-ish interface Monad<A> { static Monad<A> pure(A a); }
// Scala def pure(a: A): Monad[A]
-- Haskell pure :: (Monad m) => a -> m a
// Java-ish List.pure("Hello") == List.of("Hello");
// Scala List.pure("Hello") == List("Hello")
-- Haskell pure "Hello" :: List String == ["Hello"]
// Java-ish Optional.pure("Hello") == Optional.of("Hello");
// Scala Option.pure("Hello") == Some("Hello")
-- Haskell pure "Hello" :: Maybe String == Just "Hello"
// Java-ish Future.pure("Hello") == Future.completedFuture("Hello");
// Scala Future.pure("Hello") == Future.successful("Hello")
-- Haskell pure "Hello" :: Async String == CompletedFuture "Hello"
flatten
A.k.a join or concat
// Java-ish Monad<A> flatten(Monad<Monad<A>> a);
// Scala def flatten(a: Monad[Monad[A]]): Monad[A]
-- Haskell concat :: (Monad m) => m (m a) -> m a
// Java-ish List.of(List.of(1,1), List.of(2,2)).flatten == List.of(1,1,2,2);
// Scala List(List(1,1), List(2,2)).flatten == List(1,1,2,2);
-- Haskell concat [[1,1],[2,2]] == [1,1,2,2]
// Java-ish Optional.of(Optional.of("Hi")).flatten == Optional.of("Hi"); Optional.of(Optional.empty).flatten == Optional.empty;
// Scala Some(Some("Hi")).flatten == Some("Hi") Some(None).flatten == None
-- Haskell concat Just (Just "Hi") == Just "Hi" concat Just Nothing == Nothing
// Java-ish Future.of(Future.of("Hello")).flatten == Future.of("Hello");
// Scala Future.successful(Future.successful("Hello")).flatten == Future.successful("Hello")
-- Haskell concat (Async (Async "Hello")) :: Async (Async String) == (Async "Hello")
flatMap
Also known as bind or >>=
// Java-ish interface Monad<A> { Monad<B> flatMap(Function<A, Monad,B>> fn); }
// Scala trait Monad[A] { Monad[B] flatMap(fn: A => Monad[B]): Monad[B] }
-- Haskell bind :: (Monad m) => m a -> (a -> m b) -> m b
[1,2,3].flatMap(a => [a, a]) // Returns [1,1,2,2,3,3] Maybe(1).flatMap(value => Some(value+2)) // Returns Maybe(3) Maybe(1).flatMap(value => None) // Returns None
bind-- >>= is pronounced bind in Haskell. Wat! [1,2,3] >>= \a -> [a, a] -- Returns [1,1,2,2,3,3] Just 1 >>= \value -> Just (value+2) -- Returns Just 3 Just 1 >>= \value -> Nothing -- Returns Nothing
// Scala for { a <- Some(5) b <- Some(10) } yield a + b // Returns: Some(15)
-- Haskell do a <- Just 5 b <- Just 10 pure a + b -- Returns: Just 15
// Java-ish Function<String, String> toUpperCase = ...; Function<String, String> trim = ...; Function<String, String> toUpperCaseAndtrim = toUpperCase.compose(trim);
// Scala def toUpperCase(input: String): String = ... def trim(input: String): String = ... def toUpperCaseAndTrim = toUpperCase.compose(trim)
-- Haskell toUpperCase :: String -> String trim :: String -> String toUpperCaseAndTrim = toUpperCase . trim
// Java-ish Function<String, Promise<String>> toUpperCase = ...; Function<String, Promise<String>> trim = ...; Function<String, Promise<String>> toUpperCaseAndTrim = toUpperCase.monadCompose(trim);
// Scala def toUpperCase(input: String): Promise[String] = ... def trim(input: String): Promise[String] = ... def toUpperCaseAndTrim = monadCompose(toUpperCaseService, trimService)
-- Haskell toUpperCase :: String -> Promise String trim :: String -> Promise String toUpperCaseAndTrim = toUpperCase >=> trim
In most languages sequence is implied:
def main() = { // Executes doA then doB then doC and then returns val a = doA(); val b = doB(); val c = doC(); a + b + c }
-- Executes in an arbitrary order as everything is lazy main = let a = doA b = doB c = doC in a + b + c
-- Executes doA then doB then doC and then returns a result main = do a <- doA b <- doB c <- doC pure a + b + c
// Java-ish Integer add(Integer a, Integer b) { System.out.println("Hello World!"); return a + b; }
// Scala def add(a: Int, b: Int): Int = { println("Hello World!") a + b }
add :: Int -> Int -> Int add a b = do println "Hello World" -- ERROR! Doesn't compile a + b
add :: Int -> Int -> IO Int add a b = do println "Hello World" -- Compiles pure $ a + b
flatMap lets us compose that type.flatMap from Monad!They are Monads!
But that's the boring bit about them.
They enrich a type to give it extra functionality
Maybe adds nullList adds multiple valuesPromise adds asynchronyReader adds dependency injectionWriter adds loggingState adds arbitrary stateIO adds the ability to use the computerThis has nothing to do with Monads!
// Java-ish Integer value; // A boring integer List<Integer> values; // An exciting bunch of integers! Future<Integer> value; // An exciting asynchronous integer!
// Scala value: Int; // A boring integer values: List[Int]; // An exciting bunch of integers! value: Future[Int]; // An exciting asynchronous integer!
-- Haskell value :: Int -- A boring integer values :: List Int -- An exciting bunch of integers! value :: Async Int -- An exciting asynchronous integer!
// Java-ish Maybe<T> List<T> Promise<T> Reader<T> Writer<T> State<T> IO<T>
// Scala Maybe[T] List[T] Promise[T] Reader[T] Writer[T] State[T] IO[T]
-- Haskell Maybe t, List t, Promise t, Reader t Writer t, State t, IO t
They're a Design Pattern for a specific type of composition.
A Monad implies a whole bunch of things
Try to learn them a little bit at a time!
It's ok to be overwhelmed!
This stuff takes time to learn.