Inventing Monads
stopa.io
stopa.io
Before I understood monads, I read variations of the above sentence a million times, always got stuck here: https://i.imgur.com/McThkuh.png
How does this abstraction let us perform IO, do in-place destructive updates, etc? The answer is that it doesn't. These must be primitives supplied by the runtime. A monad like IO has its two functions (return+bind), and a bunch of other magic functions. It's obvious now but that fact is hardly ever stated.
I wonder if anyone has tried explaining from the other direction? Rather than building up to Maybe, try building down from a desire to print Hello World.
I once spent a day thinking I had misunderstood something fundamental, but really I just had an errant tab instead of a space.
The number of times I've used curly braces, single lines or expression substitution to bail me out is many...
"This is not by coincidence: people learning Haskell usually have problems with point-free style not because of the composition operator per se, but because code is suddenly "reversed". Other communities such as F# have adopted the "pipe forward" operator instead of composition as the default style."
That's why i prefer the reverse application operator & to .
As confusing as operator precedence can get, I do see the beauty in "programmable whitespace" but to get anywhere close in other language syntaxes you need to do even more perverse things like here [1]
If I did have to choose a different syntax for haskell it would be S-expressions rather than Algol so perhaps Hackett from Lexi Lambda
For some kinds of monads, like maybe, that’s all you need.
But, some other kinds of monads need more logic. List is one, IO is another.
In OOP language, sometimes all you need is the parent class. But sometimes you need to extend the parent and add more.
https://hackage.haskell.org/package/base-4.14.0.0/docs/src/G...
You can write `getChar` as many times as you want, and you will get the same result every time, and it will have nothing to do with whatever character is being sent to stdin. Instead, what you will get is an instruction, saying "please get a character from stdin".
What the IO monad allows you to do is to compose instructions together. For example, you could write `getChar >> getChar". This makes a new instruction that says "please get a character from stdin, discard it, then get a character from stdin". The `>>` operator means "follow the instruction on the left, discard the result, then do the instruction on the right".
Your entire program is made by composing instructions like this, into one new huge instruction that specifies your entire program's behaviour. You assign that to the special name `main`. At runtime, the instruction is executed.
The only bit of compiler magic that's necessary is the code for interpreting the instructions and actually executing them. In theory, although haskell doesn't let you do this, you could write `getChar` yourself, and just return the same thing that `getChar` does, and it would behave identically.
This I feel is the simplest explanation for why monads are necessary in Haskell. They allow you to conveniently specify what you want your program to do, using only pure functions and values. Why do you want all values to be pure? Many don't because it's kind of a pain, but Haskell kind of exists to see if a lazy, pure functional language can be fun to use and many find that it is. Hope this helps someone!
What's the difference?
The answer is that we can, and Haskell used to work this way [1]! So as 'chowells' observed the monad-part of IO is almost incidental.
1: https://stackoverflow.com/questions/17002119/haskell-pre-mon...
Because you can't pass results from a earlier list element into the computation of a later element. You'd need a list with existentially-quantified element types such as:
{-# LANGUAGE ExistentialQuantification, GADTs #-}
foreign import ccall "exit" exit :: Int -> IO ()
infixr 1 :>
data IOList a = forall b. (:>) (a -> Act b) (IOList b)
data Act b where
Getchar :: Act Char
Print :: String -> Act ()
Exit :: Int -> Act ()
main' :: IOList ()
main' = (\_->Getchar)
:> (\c->Print ("Got '"++[c]++"'\n"))
:> (\_ -> Exit 0)
:> undefined
runio :: a -> IOList a -> IO ()
runio a (f:>fs) = flip runio fs =<< case f a of
Getchar -> getChar
Print s -> putStr s
Exit n -> exit n
main = runio () main'
where f :> g :> h :> ... is your list of instructions (rather than f : g : h : ..., which doesn't allow the response type of f to be related to the argument type of g).I'm favoriting this comment so I can come back the next time I forget how all this works :)
Everything I've seen is monads are confusing because they solve a problem imperative languages don't think they have. As the article points out,
function getDisplayPictureFromId(id) {
const user = getUser(id)
if (!user) return
const profile = getProfile(user)
if (!profile) return
return getDisplayPicture(profile)
}
is totally fine and idiomatic in most imperative languages. Once a programmer starts chaining functions together (as functional programmers are wont to do) then the "if (!user) return" become a substantial blocker that needs a solution - which leads very quickly to monads and abstracting away IO.This would be:
function getDisplayPictureFromId(id) {
return getUser(id)?.profile?.displayPicture
}
If the language does not have the null-safe operator '?.' you just write it: function nullSafe(obj, callback) {
if (obj) {
return callback(obj)
}
return null
}
function getDisplayPictureFromId(id) {
return nullSafe(getUser(id),
(user) => nullSafe(user.profile,
(profile) => profile.displayPicture)
))
}
Not as nice in this case, but with a few similar functions for composing other functions, you achieve similar benefits to Monads without having the complexity of higher-kinded types.- cultural reliance on lambdas as the universal force (nothing against lambdas, but outside the fp world, there will be too much culture shock)
- representation as computation, the monad is only kinda building a graph to be evaluated, and each node can be wrapped with rules as you see fit
That's what the free monad does. Not every monad is a free monad, IO certainly isn't.
In lisp/fp it was the "only" feature. People were thinking in how to encode anything with lambdas and not with the usual imperative traits (prog, do, loop). Consider that in 75, the first scheme paper was already talking about CPS (aka pre-monadic style).
I'm not making value judgement, it's just that using parameterized code blocks in LINQ select/where or java streams or python map/reduce is just the very first step in a long series of stairs. And monads are on the 3rd floor kinda.
So to the fp crowd it's business as usual, but for the rest of the world it's a twisted idiom at first.
Turing Machine and Lambda Calculus are two entry points into computational thinking, is it really so alarming that if you've only learned one that trying to learn the other will make you feel like a beginner all over again?
When first learning, the most common abstractions are variations on "a list of steps" - like an instruction manual or To-do list - without touching on memory.
I wonder if there's some sort of similar abstraction here that could be used instead, for the just-getting-started stage...
In imperative steps of code, time flows down your code.
In functional code your flow of time is each function application.
Why not start enumerating the lines of a file, and show how we're building up a list of thinks, and there is no actual variable representing the line numbers because state is for Statists?
I feel it is the same with monads including all the false suggestions that one might need to understand category theory and similar such nonsense.
Badabing badaboom, the essence of dependency injection
They're very closely related concepts - both abstractly saying "let me define unit of logic in terms of how it composes passed-in units of logic", but the distinction between "objects that need to be instantiated and injected at construction time" and "functions that are passed in at runtime" is pretty large.
i.e:
You can think of a class as a higher order function, that takes in a list of arguments (constructor), and returns a list of functions, that are defined within the closure of those arguments
--
Hence, to me, the essence of these things are the same -- thought you are right that when people talk about dependency injection, they are also implying a specific way that these dependencies are provided (by the framework, usually magically)
Dependency injection is one of the techniques used to implement inversion of control.
> I've always thought of Dependency Injection as about "declaring a class in terms of its dependencies' interfaces, but allowing a framework to take responsibility for instantiating the actual dependency objects" - whereas "a function that accepts a function" is IoC (e.g. https://kentcdodds.com/blog/inversion-of-control/)
Nope, dependency injection is about passing dependencies to the objects that depends on them (i.e., inject the dependency), instead of letting the object itself instantiate them directly or actively request access to them.
You may or may not depend on a framework to manage dependencies (service locator) and pass them to instances (service injector) but those are just helper components that assist with the whole dependency injection workflow. The key point is that dependencies are passed to the objects that depends on them.
I think OOP has more primitive concepts (and more mutation) than FP, so dependency injection in OOP also includes object construction and often mocking effectful operations. That's why it gets its own name in OOP, while being more of an ambient idea in FP.
As an alternative, I would suggest that `display` is special, and that instead of thinking of passing an extra parameter to `display`, that the module that calls it should instead have `display` itself injected.
Relying on dynamic variables would probably not be considered dependency injection. A major purpose of DI is that dependencies should be declared in the signature, making it explicit which dependencies a unction or object depends on.
Yes, and a coeffect system would cause dependencies on dynamic variables to be declared statically, even if they're provided by "the environment" at runtime.
Tomas Petricek's PhD project page is a good introduction to coeffect systems, and it illustrates dynamic variables as an example. http://tomasp.net/coeffects/
It’s definitely unnecessary to understand category theory to work with monads. What is helpful, however, is having some comfort with math.
What does that mean exactly? It’s a level of comfort in working with definitions, properties, operations, special elements, proofs. People can get so frustrated because they think they don’t understand what a monad is. Like they want to hold it in their hand the way they would an apple or a tennis ball.
When you’re comfortable with math you kind of lose that need to think about an object concretely. You start to only care about the definitions, properties, axioms, laws, theorems, etc that concern a particular object. Then you just play around with a few examples and see the implications of these things. That’s all there is to it. The power comes from the abstraction. It can take time to become comfortable with abstract concepts though.
Written by a 3-time Oscar winner, no less!
Sorry, what? Their About page is blank.
2014 - Technical Achievement Award, shared with Olivier Maury, Ian Sachs: For the creation of the ILM Plume system that simulates and renders fire, smoke and explosions for motion picture visual effects.
2001 - Technical Achievement Award, shared with George Borshukov, Kim Libreri: For the development of a system for image-based rendering allowing choreographed camera movements through computer graphic reconstructed sets.
- Algebraic data types
- OCaml's let expressions
- F#'s computation expressions
I also think that it is important to write something that naturally requires the use of monads. One thing that spring to mind is Parser Combinators.
- https://fsharpforfunandprofit.com/posts/understanding-parser...
Shameless plug for my own article that did something similar about a week ago: https://medium.com/@ameltzer91/an-easy-to-understand-monad-g...
It does have some minor shortcomings in my opinion (e.g. if you haven't already started on the path of reinventing monads yourself you may struggle to immediately understand why this is a big deal and how it will save your life, the author should have used LanguageExt instead of their own library as LanguageExt is actively maintained and extremely well thought out, the book stops just short of becoming practical in the sense of "here's how to start a new C# project while thinking in functions", etc.).
You might also consider the LanguageExt guide itself: https://github.com/louthy/language-ext/wiki/Thinking-Functio...
I spent some time explaining the advantages of an FP approach to my colleagues and LangExt has started to pop up in their PRs, which I'm very happy about
It's definitely a great library and I'm really impressed with the effort that's being put into it!
I'll give the book you're suggesting a read. I think it might help cover some of the gaps in my knowledge, which is exactly what I'm looking for, so thank you for the suggestion
try:
user = getUser()
profile = getProfile(user)
pic = getProfilePicture(profile)
thumb = getThumbnail(pic)
return thumb
except Missing:
return NoneFew of them are actually bound to syntactic sugar known as "do-notation", that let's you write that sequenced code in post as if you were binding simple variables, adding branching, effectful statements or auxiliary definitions along the way. This really pays out when you start turning simple monads into so-called "monad transformers", that let you stack multiple behaviours/wrappers on top of each other, keeping the same pretty do-notation untouched.
EDIT: I have serious problems with the post, because a) it claims that discovering one application of monads is understanding monads, b) for me the true strength of monads shines in strictly typed languages, everything else is just an approximation of the concept.
Notice that that short introductory article already has examples of two different monads. People use many more of them.
So the ability to define this independently isn’t useful. You want to create a standard. Whether that goes into the standard library, or gets a little language syntax support as well, is something you can argue about, but is pretty arbitrary and probably just comes down to the style of the language.
The burden on the developer is equal: the hard part is learning the conceptual patterns, how to compose solutions in terms of them, and how other libraries you use expect you to use them.
Monads aren't about handling error. That's just one of two examples on the article (and yes, that one is on the standard library) and it's used for more stuff than errors already.
> You want to create a standard.
Monads are a standard. That's basically all they have into them. If you do it right, most of your monadic code won't even know what monad it's running in.
Not really. That's maybe the impression you might, but it's not true. The "language syntax support" isn't really syntax, it is a deep conceptual change of the language. Now instead having functions return one value, they _can always return 2 different types_ and you don't know if they really do unless you know the implementation (which sometimes you can't).
That not only causes pain on a daily basis for most developers, it especially causes pain for library authors (which you are maybe not, so it is not as visible to you) and it especially causes troubles down the road with other concepts.
As example, check out Java's try-with-resources. It is a bandage over a bandage and while it improves things it is difficult to get right and has a lot of corner cases and really strange behaviour, especially in combination with constructors.
With plain simple language features, something like that does not happen.
A lot of times that's a perfectly fine decision, but six months down the road when the code has grown a lot, you might find an alternative way to get the resource on a fail
If you made a decision to throw an exception immediately after failing to get the resource, you then have to either rewrite the logic, which can be very expensive, or catch the error, which bloats the code (throwing the exception is now redundant and is fixed by adding code that catches that exception)
By instead putting the return value in an appropriate monad, you can postpone throwing the exception until you're sure that there's no way to recover
Throwing an exception is still something that's necessary occasionally, but it should not be done until there's no possible way to recover, and be done in a way, so it's easy to rewrite if a way to recover becomes available at some future point in time
Say you're working with some data structure that contains/emits numbers: a pointer to a resource containing a number. A list of numbers. A function that returns a number. An optional number (or null).
A common operation is unpacking that structure to get a number, applying a function to the number, and packing it back up: Reading from the pointer, applying the function, and returning a pointer of the result. Applying the function to each element of the list and returning a list of the results. Composing a function with another function. Applying a function to the optional number or just returning the null.
When you're writing code on this, it's error prone to do the unpacking, application, and repacking. It's much simpler if you can write code that looks like `def f(x): return exp(x)/x + 23`. Much more testable too. If you have two or more of these structured things, it might get even more error prone. It's much easier to write code that takes three integers and does stuff, instead of writing code that takes three pointers/lists/functions/optionals.
Monads are part of a hierarchy that abstracts that. Anything that defines that sort of function application in a particularly convenient way is a monad. There's more to it, but that's why it's useful.
It lets you write code dealing with the things in your data structure, letting you mostly ignore the structure itself.
-------
In this specific situation, say you want to replace your error handling with something else. Maybe it writes to a log file then errors. Or maybe it does something fancier. Or maybe you even change the way you get the resource as well as the erroring to something fancy. As you swap out the "structure" code, with a monad it's just switching to a new monad, rather than refactoring the business logic related code. It's a nice separation of concerns.
Aaand with this statement you skipped over what IMO is the most important missing piece, because everything above it fits higher-order functions such as map(), which as far as I understand aren't monads.
tl;dr map works for applying the simplest functions to "containers." To apply more interesting functions, you need bind, pure/return, and/or whatever applicative's <*> is called.
Let's say you're working in something like Maybe. You might want to write some code like
f :: Float -> Float
f x = 5 + x
In that case map is fine. fmap lets you focus on simple code like that. Or maybe you want to write f :: Float -> Maybe Float
f x = if x == 0
then None
else 5 / x
In that case you need more than map, because you don't want to deal with what map would give you: a Maybe (Maybe Float). (>>=) lets you still focus on simple code like this, since you dont have to deal with any unpacking/flattening, which was what I'm saying is why monads are so useful.More importantly, you need more of the FAM hierarchy than map if you care about multivariate functions, which I'd say is most code. Lets say we're working with
f :: Float -> Float -> Float
f x y = x + y
We want to write code like that and use some version of function application (like map). If we just use map, we get the following f <$> maybeX :: Maybe (Float -> Float)
which isn't at all what we want, because we can't apply it to a maybeY (or even to a float y, which `pure`/`return` lets us treat as a maybeY). If we define an additional way to apply that Maybe (Float -> Float) to a Maybe Float, we've defined Applicative, forcing us to go beyond a functor.The motivation and usefulness is the same throughout: we just want to write code and apply functions that don't care about the structures emitting/containing our inputs and outputs. It just turns out that there are three cases depending on the kinds of functions we're writing and applying
f :: a -> b -- functor is sufficient, like you say.
f :: a -> b -> c -- functor isn't sufficient. applicative is.
f :: a -> m b -- functor and applicative aren't sufficient. monad is.
I wrote a series of posts ages ago deriving all these from that one motivation (in a more fleshed out manner) http://imh.github.io/2016/05/26/why-monads.htmlNope. I think this is why you don't see where the previous post falls short, too much familiarity so you don't realize you're skipping over important aspects.
> so I'm going to cross my fingers that you know it in haskell and use that notation and terminology to save us time.
I get just enough Haskell to understand the first 3 code blocks, and can guess what the 4th is depicting, but am not sure.
But there are many other examples that are useful in practice (IO, streams, parsers, lists, operations in context, futures, etc as mentioned in other threads). A monad is the interface you need to implement for each of these to compose nicely.
Then one day you'll need to compose Foo's, so you'll ask yourself "is there a monad for Foo's?" and if there is the code generally writes itself.
Monads can help readability by focusing on the composition of your domain logic instead of writing out the same low-level boilerplate over and over again.
> hello, quick question: i feel like there's a more elegant way to express this, but I'm struggling to come up with one:
foo :: (a -> Bool) -> (a -> Bool) -> a -> Bool
foo f g x = f x && g x
For those who aren't as familiar with Haskell, that is taking a function f and a function g, passing argument x to both, and then doing a binary and on their boolean result.One answer suggested:
> You can get fancy, but the “simple” version is almost always more readable:
(not . null . f $ x) && (not . null . g $ x)
Which I find myself agreeing with in many ways, but for some reason leaves me desiring more.The answer to that desire complements this article I think:
> if f and g returned Maybe you could have:
> f _ = Nothing
> g _ = Just ()
> x = ()
> (f x) <*> (g x)
Nothing
> import Data.Maybe
> (f x) <*> (g x) & isJust
False
> -- or to avoid <*> you can do
> liftA f g x
Nothing
If that's not clear, let me know what's confusing and I'll try to explain further fpam :: [(a -> b)] -> a -> [b]
fpam fns v = fns <*> pure v
after that then fold the list with boolean && foo :: (a -> Bool) -> (a -> Bool) -> a -> Bool
foo f g x = foldr1 (&&) ( fpam [f,g] x)
or alternatively with no helper functions foo2 :: (a -> Bool) -> (a -> Bool) -> a -> Bool
foo2 f g x = foldr1 (&&) ( [f,g] <*> pure x)
for example Prelude> foldr1 (&&) ( [ (==3), (==4) ] <*> pure 3 )
False
Alternate implementations: import Data.List
import Data.Function
import Control.Monad.State
import Data.Foldable
import Control.Arrow((>>>))
import Control.Monad.Reader
import Control.Monad.List
applyList :: [(a -> a)] -> a -> a
applyList list = execState $ for_ list modify
applyList2 :: [(a -> a)] -> a -> a
applyList2 = foldr1 (>>>)
fpam :: [(a -> b)] -> a -> [b]
fpam fns v = fns <*> pure v
fpam2 :: [(a -> b)] -> a -> [b]
fpam2 fns = runReader $ forM fns reader
fpam3 :: [(a -> b)] -> a -> [b]
fpam3 fns v = fmap (\f -> f v) fns
fpam4 :: [(a -> b)] -> a -> [b]
fpam4 fns = runReaderT $ do
fn <- lift fns
reader fn foldr1 (&&) ( [ (==3), (==4) ] <*> pure 3 ) Prelude> import Data.Function (on)
Prelude Data.Function> :t on
on :: (b -> b -> c) -> (a -> b) -> a -> a -> c
Prelude Data.Function> f = undefined :: Int -> [Int]
Prelude Data.Function> g = undefined :: Int -> [Int]
Prelude Data.Function> :t ((&&) `on` (not . null . ($ x))) f g
((&&) `on` (not . null . ($ x))) f g :: Bool
Alternatively in Control.Arrow there's a 'fanout' operator &&&: Prelude Control.Arrow> f = undefined :: Int -> Bool
Prelude Control.Arrow> g = undefined :: Int -> Bool
Prelude Control.Arrow> :t uncurry (&&) . (f &&& g)
uncurry (&&) . (f &&& g) :: Int -> Bool
Of course it's still probably simpler to just write it out in this case. class Predicate a where
(&&) :: a -> a -> a
not :: a -> a
etc :: ...
and then define the obvious instances for `Bool` and `Predicate b => a -> b`It's really nice to be able to just write `(> 6) && even` or `isAlpha || (== '_')`
That's really not true, is it? I mean, you only need to look at how monads are used to express results as return types to understand the practical usefulness of monads.
Functions, continuations, state, IO, parsers, futures, streams, lists, transactions, LINQ, observables.
I can only think of one abstraction more useful - the function.
What is so unsatisfactory about them?
Patterns like the monad are so abstract, that ob its own nobody knows what to do with it, but it makes them so very powerful.
OOP patterns, on the other hand, come from a more inductive source. They are more concrete, but also not as powerful. Easier to grasp, but less concise.
We need a step (or more) between the definition of FP concepts and their application, to make all this more approachable for the average programmer.
https://homepages.inf.ed.ac.uk/wadler/papers/marktoberdorf/b...
https://homepages.inf.ed.ac.uk/wadler/papers/marktoberdorf/b...