/posts/haskell-monads/
In functional programming, Monads are an abstraction used to structure programs
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:
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:
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)
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 ()
Eg. Error Handling We define all types of errors we want to catch and throw
as MonadicError
We define a type for functions that may throw a MonadicError
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
If (>==)
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
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