[HN Gopher] Inventing Monads
       ___________________________________________________________________
        
       Inventing Monads
        
       Author : stopachka
       Score  : 66 points
       Date   : 2020-08-29 18:22 UTC (4 hours ago)
        
 (HTM) web link (stopa.io)
 (TXT) w3m dump (stopa.io)
        
       | centimeter wrote:
       | I think the hard part for most people is moving beyond
       | Maybe/Either. It's hard to find motivating examples when most
       | languages lack denotational semantics and everything implicitly
       | happens inside IO all the time.
        
         | Scandiravian wrote:
         | That is something I've struggled with as well. Do you have any
         | suggestions for resources, that could help overcome that
         | challenge?
        
           | centimeter wrote:
           | Nothing off the top of my head. The way I really got monads
           | was just writing Haskell code. It's one of the very few
           | programming languages with a clear denotational semantics for
           | everything (as a result of being pure), and you end up with
           | lots of simple problems like "how do I keep this value around
           | and 'mutate' it" that are conveniently addressed by a monad
           | like the State monad.
        
           | ryanjshaw wrote:
           | If you come from a C# background, this book is excellent:
           | https://www.manning.com/books/functional-programming-in-c-
           | sh...
           | 
           | 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...
        
             | Scandiravian wrote:
             | I actually found the LangExt package a few months ago, when
             | I started a new job and had to use C# as the default
             | language (I've primarily been using Python, Typescript, and
             | Rust in my previous jobs)
             | 
             | 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
        
       | fizixer wrote:
       | So do you need monads in Haskell because that's the only way you
       | could do some of the things that are trivial in an imperative
       | language (albeit verbose and arguably inelegant)?
        
         | Koshkin wrote:
         | Yes. (For example, ML, being impure, does not need this.)
        
         | mrkeen wrote:
         | No
        
         | exdsq wrote:
         | This is the paper that introduced them to FP, if you're
         | interested in the original reasoning.
         | 
         | https://homepages.inf.ed.ac.uk/wadler/papers/marktoberdorf/b...
        
       | ape4 wrote:
       | That code that "is getting pretty ugly" seemed ok to me.
        
       | tarkin2 wrote:
       | [Edited away]
        
       | touchngthevodka wrote:
       | As someone who isn't familiar with functional programming, what
       | benefit does this give us over throwing an error when trying to
       | access a resource that doesn't exist?
        
         | ianhorn wrote:
         | A monad's special function application lets you write much
         | simpler code in certain situations.
         | 
         | 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 you 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.
        
         | raiflip wrote:
         | What if you need to access 5 resources in a row, any of which
         | can throw exceptions? A monad centralizes the repeated logic in
         | its flatMap function.
        
           | edflsafoiewq wrote:
           | I think the comparison is to something like this
           | try:         user = getUser()         profile =
           | getProfile(user)         pic = getProfilePicture(profile)
           | thumb = getThumbnail(pic)         return thumb
           | except Missing:         return None
        
             | raiflip wrote:
             | That example seems a bit odd as a design. You're throwing
             | an exception to represent a no result, but suppressing it
             | to convert the exception to a none. Monads give you the
             | convenience while being consistent.
        
               | Koshkin wrote:
               | Well, the problem here is that using exceptions to
               | implement the program's logic is considered bad practice.
        
         | TheMatten wrote:
         | Well, nothing stops you from implementing equivalent wrapper
         | using error mechanics (that could actually make it faster in
         | some cases), but turning this idea into first-class value
         | allows you to abstract over it easily. E.g. in Haskell, base
         | library comes with lots of functions for manipulating monadic
         | wrappers in generic ways, mapping, sequencing and threading
         | through them in common way (once you start using them, you
         | actually realise that a lot of business logic that looks
         | perfectly reasonable in common languages ends up being
         | boilerplate that can be avoided easily using simple
         | combinator).
         | 
         | Few 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.
        
         | jlg23 wrote:
         | WRT this specific post: Graceful error propagation with zero
         | boilerplate code for passing through errors. If all you need to
         | know is "nope" at the end of some computation, you won't have
         | to handle all different "nopes" you encounter en route. Or the
         | other way round: You can write lots of code without checking
         | input values (for null, in this case) because you have a
         | guarantee it won't be called if the input value is invalid
         | (null here).
         | 
         | 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.
        
         | marcosdumay wrote:
         | One is just plain code that you write on a library. The other
         | is a special construction on the compiler that will solve this
         | specific use case. Your question is on the wrong way around.
         | You should be asking why do you need specialized compiler
         | support for just that use case.
         | 
         | Notice that that short introductory article already has
         | examples of two different monads. People use many more of them.
        
         | Scandiravian wrote:
         | Throwing an error is implicitly making a decision, that there's
         | no way to recover to a working state for the program
         | 
         | 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
        
         | lmm wrote:
         | You need a special language feature to "throw an error", which
         | can break your reasoning about code. A seemingly harmless
         | refactor like swapping two lines might completely change your
         | behaviour because one of those lines actually threw an error.
         | It becomes very difficult to do things like manage a resource
         | properly (ensuring it's always released), to the point that you
         | probably end up adding more special language features to handle
         | that.
        
       | raiflip wrote:
       | I really like how this article builds up from a simple real life
       | use case.
       | 
       | Shameless plug for my own article that did something similar
       | about a week ago: https://medium.com/@ameltzer91/an-easy-to-
       | understand-monad-g...
        
         | stopachka wrote:
         | Thank you for the kind words, and nice article! : )
        
         | jjjbokma wrote:
         | Nice article, I linked to it from my tumblelog
         | https://plurrrr.com/archive/2020/08/19.html 10 days ago. Thanks
         | for writing this.
        
       | k__ wrote:
       | I think, the main benefit of FP is alsoits main problem.
       | 
       | 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.
        
       | cjfd wrote:
       | In various places on the web one can find the quote that
       | 'Dependency injection is a 25-dollar term for a 5-cent concept'.
       | 
       | 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.
        
         | chongli wrote:
         | _suggestions that one might need to understand category theory
         | and similar such nonsense_
         | 
         | 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.
        
           | Koshkin wrote:
           | Another thing that is often missed is that we think we
           | "understand" something when in fact we just got used to it.
           | Even such simple concept as "number" would probably be very
           | difficult to _explain_ to someone (who either doesn't know
           | what a number is or wishes to "really understand" it).
        
         | edflsafoiewq wrote:
         | If only DI had a clear definition like "monad" does, I might be
         | able to understand what it is.
        
           | goto11 wrote:
           | It just means passing a dependency as an argument to method
           | or constructor. Seriously, it is just that!
        
             | agumonkey wrote:
             | I used to restrict DI as dynamic binding but someone told
             | me that for business, having a special category of
             | arguments to be tweaked as see fit is useful. It's variable
             | at the system level maybe ?
        
             | [deleted]
        
             | Twisol wrote:
             | Which, in FP, is all but invisible: the arguments to a
             | function literally are its dependencies.
             | 
             | 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.
        
           | stopachka wrote:
           | Imagine a function, that accepts another function as an
           | argument.
           | 
           | Badabing badaboom, the essence of dependency injection
        
             | scubbo wrote:
             | Isn't that Inversion Of Control, not Dependency Injection?
             | 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/)
             | 
             | 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.
        
               | stopachka wrote:
               | I think the distinction may blurry a bit, if you consider
               | classes as a fancy way of writing higher order functions.
               | 
               | 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)
        
       | chowells wrote:
       | There's also the classic http://blog.sigfpe.com/2006/08/you-
       | could-have-invented-monad...
       | 
       | Written by a 3-time Oscar winner, no less!
        
         | guerrilla wrote:
         | > Writen by 3-time Oscar winner, no less!
         | 
         | Sorry, what? Their About page is blank.
        
           | dllthomas wrote:
           | The author is Dan Piponi: https://pwlconf.org/2018/dan-
           | piponi/
           | 
           | https://www.imdb.com/name/nm0685004/
        
         | dang wrote:
         | Looks like there has been only one small discussion from 2009:
         | https://news.ycombinator.com/item?id=958789
        
       | ducaale wrote:
       | To understand monads, I think it helps to know a language where
       | expressing such a concept is more natural. Although I do not
       | claim to know 100% what monads are yet, coming across these
       | features made it easier for me to understand the concept a little
       | bit.
       | 
       | - 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...
       | 
       | - https://www.youtube.com/watch?v=N9RUqGYuGfw
        
       | ridiculous_fish wrote:
       | > This begins to get us to the fundamental abstraction of a
       | monad: a box, with an interface for map, and flatMap
       | 
       | 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.
        
         | chowells wrote:
         | I think most of this happens because lots of people say things
         | like "the IO monad" when they actually just mean "IO". It
         | misleads people into thinking the monad part is important. It
         | isn't. IO is the tough concept. Monad is almost trivial.
        
           | ridiculous_fish wrote:
           | Good point, I agree. But you must learn it to get anything
           | done. Grappling with IO + monad + do-notation all at once is
           | hard!
           | 
           | I once spent a day thinking I had misunderstood something
           | fundamental, but really I just had an errant tab instead of a
           | space.
        
             | chowells wrote:
             | Ok, yes. Monads as a concept are almost trivial in
             | comparison to thinking in terms of the IO type. But that
             | doesn't mean they are easy and don't add more hurdles to
             | get over. I think I undersold how daunting it can be to
             | face all the new things at once, and you're right about it.
        
             | yomly wrote:
             | As a recent Haskell learner, I found the indentation part
             | of Haskell hard to learn and a little undiscoverable/hard
             | to google.
             | 
             | The number of times I've used curly braces, single lines or
             | expression substitution to bail me out is many...
        
             | dllthomas wrote:
             | + type inference + return-type polymorphism!
        
         | goto11 wrote:
         | Yeah this is like saying objects lets us perform IO in Java,
         | and then going deep into explanations of what objects are and
         | what rules they obey (Liskov substitution principle etc) -
         | instead of just explaining that you call println() to write
         | "hello world".
        
           | yomly wrote:
           | We often forget how much assumed knowledge (and accepted
           | knowledge) went into learning OOP.
           | 
           | 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?
        
         | raiflip wrote:
         | A monad really is just a wrapper around a value, with flatMap
         | and return.
         | 
         | 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.
        
           | ratww wrote:
           | It's actually more like an abstract class! Even the Maybe
           | monad has some implementation, it's just super simple :)
           | 
           | https://hackage.haskell.org/package/base-4.14.0.0/docs/src/G.
           | ..
        
         | anchpop wrote:
         | What you say is correct, but let me try and explain a little
         | further. The Haskell function `getChar` is used to get a
         | character from stdin. It has the type `IO Char` and is a
         | totally, 100% pure. In fact, it isn't even a function, it's
         | just a value.
         | 
         | 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!
        
           | choeger wrote:
           | > In fact, it isn't even a function, it's just a value.
           | 
           | What's the difference?
        
             | dllthomas wrote:
             | Functions are values, but values that are not functions are
             | those that cannot be supplied an argument.
        
           | ridiculous_fish wrote:
           | Great description of IO, but I think it doesn't actually
           | motivate monads. Your example of `getChar >> getChar` could
           | be simply `[getChar, getChar]`. Why can't we just compose
           | instructions into an ordinary list?
           | 
           | 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...
        
           | thatcherc wrote:
           | Ah, thanks - this is a really nice explanation. From reading
           | a few "what is a monad" posts in the past, I get how the a
           | monad can chain functions in sequence for imperative-style
           | execution, but how the information like 'getChar' actually
           | got in was never clear. It all comes down to the runtime I
           | guess.
           | 
           | I'm favoriting this comment so I can come back the next time
           | I forget how all this works :)
        
         | agumonkey wrote:
         | to me the issue is twofold:
         | 
         | - 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
        
           | AnimalMuppet wrote:
           | Hmm. Your last line makes me think that they're, essentially,
           | using monads to represent S-expressions. That seems...
           | somewhat clumsier than just using S-expressions. You gain a
           | really good type system, though.
        
       ___________________________________________________________________
       (page generated 2020-08-29 23:00 UTC)