Monads in Haskell
In functional programming, Monads are an abstraction used to structure programs
- Help reduce complicated sequences of functions into “a pipeline” of actions
- Abstract away control flow
- Facilitate side-effects
- Manage external data interactions
class Monad m where return :: a -> m a (>>=) :: m a -> (a -> m b) -> m b
Monads are abstraction used to help structure programs and help easily achieve some functionality which would be difficult to achieve otherwise.For example, they help achieve side-effects which would be required in the real world.
Monads in Haskell are defined as a Typeclass.
We make things “monadic” by making them an instance of this typeclass. 2 main operations defined by the typeclass:
- Lifting: take a non-monadic value and turn it into a monadic value
- Binding: monadic value and function that return a monadic value
- The bind operator can have different semantics for different monads
Functors and Aplicatives
Functors are things that can be mapped over
fmap::(a -> b) -> f a -> f b
Applicatives are functors that can be applied
pure :: a -> f a (<*>) :: f(a -> b) -> f a -> f b
A monad on category C consists of an endofunctor (a functor mapping a category to itself), T: C -> C along with two natural transformations:
- 1_C -> T where 1C denotes the identity functor on C, and
- T^2 -> T where T^2 is the functor T to T from C to C
These are required to fulfill coherence conditions
class Monad m where return :: a -> m a (>>=) :: m a -> (a->m b) -> m b (>>) :: m a -> m b -> m b
Left identity: The first monad law states that if we take a value, put it in a default context with return and then feed it to a function by using »=, it’s the same as just taking the value and applying the function to it.
return a >>= f ≡ f a
Right identity: The second law states that if we have a monadic value and we use »= to feed it to return, the result is our original monadic value.
m >>= return ≡ m
Associativity: The final monad law says that when we have a chain of monadic function applications with
>>=, it shouldn’t matter how they’re nested.
(m >>= f) >>= g ≡ m >>= (\x -> f x >>= g)
Monads for Side-Effects
Eg. IO Monads in Haskell can function as “containers” that carry “extra information” apart from the value inside that functions need not worry about. Here, the “information” can be used as the action that performs IO
instance Monad IO where return :: a -> IO a (>>=) :: IO a -> a (a -> IO b) -> IO b
Example as a REPL reading/writing to a terminal
flushStr :: String -> IO () readPrompt :: String -> IO String evalString :: String -> IO String until_ :: Monad m => (a -> Bool) -> m a -> (a -> m ()) -> m () runRepl :: IO () main :: IO ()
Monads for Control Flow
Eg. Error Handling
We define all types of errors we want to catch and throw as
We define a type for functions that may throw a
type ThrowsError = Either MondaicError
Either is another instance of a monad, The “extra information” in this case is whether the error occurred.
instance (Error e) => Monad (Either e) where return x = Right x Right x >> f = f x Left err >>= f = Left err
(>==) sees an error it simply passes that error through without subsequent computations, else passes the value along
Take our 2 Error Handling and IO Monads for example, say we need to use their behavior functionality simultaneously. We use monad transformers to combine functionality of multiple monads We use ExceptT, a monad transformer that adds exceptions to other monads
newtype ExceptT e m a :: * -> (* -> *) -> * -> *
The combined Monad would then be:
type IOThrowsError = ExceptT MonadicError IO
Monads for Environment Management
Haskell has no notion of mutable state. Each function has an environment storing values for each of its args and vars.
We use a feature called IORef that helps us hold the environment state within the IO monad We then simply access this environment mutating its state as required, and keep passing it around on each evaluation cycle.
type Env = IORef[(String, IORef SomeVal)] eval :: Env -> SomeVal -> IOThrowsError SomeVal