Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do you identify monadic design patterns?

I my way to learn Haskell I'm starting to grasp the monad concept and starting to use the known monads in my code but I'm still having difficulties approaching monads from a designer point of view. In OO there are several rules like, "identify nouns" for objects, watch for some kind of state and interface... but I'm not able to find equivalent resources for monads.

So how do you identify a problem as monadic in nature? What are good design patterns for monadic design? What's your approach when you realize that some code would be better refactored into a monad?

like image 200
tonicebrian Avatar asked Jan 08 '12 11:01

tonicebrian


People also ask

What is a monad design pattern?

In functional programming, a monad is a software design pattern with a structure that combines program fragments (functions) and wraps their return values in a type with additional computation.

What is a monadic value?

In short, a monad is a way to structure computations in terms of values and sequences of computations using typed values. But since sequencing is often done by feeding one function into the next, you get some type discipline and computational leverage over what kinds of operations can follow previous operations.

What is monad in C#?

In C# terms, a Monad is a generic class with two operations: constructor and bind. class Monad<T> { Monad(T instance); Monad<U> Bind(Func<T, Monad<U>> f); } Constructor is used to put an object into container, Bind is used to replace one contained object with another contained object.

What is a monad Haskell?

What is a Monad? A monad is an algebraic structure in category theory, and in Haskell it is used to describe computations as sequences of steps, and to handle side effects such as state and IO. Monads are abstract, and they have many useful concrete instances. Monads provide a way to structure a program.


2 Answers

A helpful rule of thumb is when you see values in a context; monads can be seen as layering "effects" on:

  • Maybe: partiality (uses: computations that can fail)
  • Either: short-circuiting errors (uses: error/exception handling)
  • [] (the list monad): nondeterminism (uses: list generation, filtering, ...)
  • State: a single mutable reference (uses: state)
  • Reader: a shared environment (uses: variable bindings, common information, ...)
  • Writer: a "side-channel" output or accumulation (uses: logging, maintaining a write-only counter, ...)
  • Cont: non-local control-flow (uses: too numerous to list)

Usually, you should generally design your monad by layering on the monad transformers from the standard Monad Transformer Library, which let you combine the above effects into a single monad. Together, these handle the majority of monads you might want to use. There are some additional monads not included in the MTL, such as the probability and supply monads.

As far as developing an intuition for whether a newly-defined type is a monad, and how it behaves as one, you can think of it by going up from Functor to Monad:

  • Functor lets you transform values with pure functions.
  • Applicative lets you embed pure values and express application — (<*>) lets you go from an embedded function and its embedded argument to an embedded result.
  • Monad lets the structure of embedded computations depend on the values of previous computations.

The easiest way to understand this is to look at the type of join:

join :: (Monad m) => m (m a) -> m a 

This means that if you have an embedded computation whose result is a new embedded computation, you can create a computation that executes the result of that computation. So you can use monadic effects to create a new computation based on values of previous computations, and transfer control flow to that computation.

Interestingly, this can be a weakness of structuring things monadically: with Applicative, the structure of the computation is static (i.e. a given Applicative computation has a certain structure of effects that cannot change based on intermediate values), whereas with Monad it is dynamic. This can restrict the optimisation you can do; for instance, applicative parsers are less powerful than monadic ones (well, this isn't strictly true, but it effectively is), but they can be optimised better.

Note that (>>=) can be defined as

m >>= f = join (fmap f m) 

and so a monad can be defined simply with return and join (assuming it's a Functor; all monads are applicative functors, but Haskell's typeclass hierarchy unfortunately doesn't require this for historical reasons).

As an additional note, you probably shouldn't focus too heavily on monads, no matter what kind of buzz they get from misguided non-Haskellers. There are many typeclasses that represent meaningful and powerful patterns, and not everything is best expressed as a monad. Applicative, Monoid, Foldable... which abstraction to use depends entirely on your situation. And, of course, just because something is a monad doesn't mean it can't be other things too; being a monad is just another property of a type.

So, you shouldn't think too much about "identifying monads"; the questions are more like:

  • Can this code be expressed in a simpler monadic form? With which monad?
  • Is this type I've just defined a monad? What generic patterns encoded by the standard functions on monads can I take advantage of?
like image 76
ehird Avatar answered Oct 11 '22 09:10

ehird


Follow the types.

If you find you have written functions with all of these types

  • (a -> b) -> YourType a -> YourType b
  • a -> YourType a
  • YourType (YourType a) -> YourType a

or all of these types

  • a -> YourType a
  • YourType a -> (a -> YourType b) -> YourType b

then YourType may be a monad. (I say “may” because the functions must obey the monad laws as well.)

(Remember you can reorder arguments, so e.g. YourType a -> (a -> b) -> YourType b is just (a -> b) -> YourType a -> YourType b in disguise.)

Don't look out only for monads! If you have functions of all of these types

  • YourType
  • YourType -> YourType -> YourType

and they obey the monoid laws, you have a monoid! That can be valuable too. Similarly for other typeclasses, most importantly Functor.

like image 42
dave4420 Avatar answered Oct 11 '22 09:10

dave4420