top | item 24318832

(no title)

touchngthevodka | 5 years ago

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?

discuss

order

jlg23|5 years ago

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.

ianhorn|5 years ago

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 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.

Izkata|5 years ago

> 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.

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.

TheMatten|5 years ago

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.

Scandiravian|5 years ago

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

marcosdumay|5 years ago

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.

jmull|5 years ago

It’s not good if each library/component/framework or other unit of independently developed code uses a different error handling scheme.

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.

chombier|5 years ago

This is one example of a monad, probably not the most compelling if your language supports exceptions already (apart from having more explicit types).

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.

lmm|5 years ago

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|5 years ago

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|5 years ago

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

contravariant|5 years ago

It's the structure you need to be able to throw errors.