5 ms·
Just to expand a little bit upon the sibling comment. Let's say you need a function that maintains internal state. Now, in non-pure languages, you could that b
by pka 10y ago
Just to expand a little bit upon the sibling comment.
Let's say you need a function that maintains internal state. Now, in non-pure languages, you could that by simply referring to some global variable, but let's agree that's bad. So how can you model internal state purely?
Simple, your function takes the current state and produces a new state, along with its result. So, a pure function like:
f(String) => Int
turns into the stateful function:
f(String) => (State => [Int, State])
I.e. a function that returns a function that takes in a state and produces a result and a new state.
Cool, but now let's say we have another function g:
g(Int) => (State => [Double, State])
Let's now say we want to compose those two functions:
let result = g(f("5"));
However, this won't work, becase f produces (State => [Int, State]) but g expects Int.
So, here's bind for the State monad (implementation is not important):
bind(State => [a, State], a => (State => [b, State])) => (State => [b, State])
If we define a "type alias" (just a synonym, so we don't have to type that much) for a stateful function:
type StatefulFunction<a> = State => [a, State]
and then replace the above bind definition with it:
bind(StatefulFunction<a>, a => StatefulFunction<b>) => StatefulFunction<b>
you can see that is has the same structure as flatMap!
flatMap(List<a>, a => List<b>) => List<b>
With the type alias, f and g would then look like this:
f(String) => StatefulFunction<Int>
f(Int) => StatefulFunction<Double>
So now, here's how you compose f and g:
bind(f("5"), a => g(a))
In Haskell, there's syntax sugar for bind, so the above turns into
do
a <- f "5"
g a
which is very similar to a conventional language:
{
a = f(5);
g(a);
}
EDIT: forgot result type in bind and flatMap.
- jnordwick 10y agoThat at least gives me a base. I did wonder why you say flatmap is > flatMap(List<a>, a => List<b>) => List<b> but that isn't the same as bind, since bind took two functions and flatmap takes a list and a function. However, I do kind of understand. Compare this to the (currently) 3 responses below that quickly turn into text mush simultaneously treating me like an idiot who has never understand abstraction before or treating me like I'm been programming Haskell for a decade. At some point I'm just going to cut my losses and ignore Haskell. It isn't the language, but the less that elucidating community support.
- pka 10y agoCompare again: bind(StatefulFunction<a>, a => StatefulFunction<b>) => StatefulFunction<b> flatMap(List<a>, a => List<b>) => List<b> Look at the structure. Don't pay attention to what List or StatefulFunction actually are, that's not important here. The thing to focus on is that if you can provide this kind of general function for something, then that something is probably a monad. I.e. a function that takes in Something<a> and a function from a to Something<b> and produces Something<b>. So why is this pattern so useful that everybody freaks out about it? Well, that syntax sugar I showed you at the end, let's look at it again: do a <- f b <- g a h b The general idea here is that "a <- f" is translated into bind(f, a => ...). The whole block would turn into bind(f, a => bind(g(a), b => h(b))); Bear with me. Now, let's look at one more example: bind(Optional<a>, a => Optional<b>) => Optional<b> Optional<a> is just a data type that maybe contains an <a>. Think of it as a safe replacement for null. So, bind for Optional looks at the first argumenta and passes it on to the function and returns its result, if the Optional is not empty. If it is, bind also returns an empty Optional. Now let' say we have a bunch of Optional producing functions: getUser(userId) => Optional<User> // user may not exist getAddress(User) => Optional<Address> // user may not have entered an address getStreet(Address) => Optional<Street> // address may be weird Now ok, what would we do in a language without monads? user = getUser(userId); if (user.hasValue()) { address = getAddress(user.getValue()); if (address.hasValue()) { street = getStreet(address); // do something } } Well, that's not great. But because Optional is a monad and we have that nifty bind sugar, that's how that code would look like in Haskell: do user <- getUser userId address <- getAddress user street <- getStreet address Much better. More generally, it turns out monads are literally everywhere. The guy that talked about CPS transformed programs being monads is right - that's the continuation monad. Let's look at that. If you know node.js, you know that its API is basically asynchronous: fs.open(path, callback) fs.read(fd, ..., callback) fs.close(fs, callback) Now, let's read some file in node: fs.open(path, function(fd) { fs.read(fd, ..., function(result) { // do something with result fs.close(fs, function() { // file closed } } } Well, that's ridiculous. But becase we have the continuation monad, we can write exactly the same thing in Haskell: do fd <- open path result <- read fd // do something close fd So why are monads so useful? Because they are so general. They can be used to abstract over many many things that exhibit the basic pattern (S<a>, a => S<b>) => S<b>. Not to mention that when you write a function to generally work with monads, it will work for ANY monad. List, State, Maybe, Cont - doesn't matter. There's not much more to it than recognizing that pattern. That's it. Once you see that that's literally it you'll start seeing monads everywhere :)
- clock_tower 10y ago> Let's say you need a function that maintains internal state. Now, in non-pure languages, you could that by simply referring to some global variable, but let's agree that's bad. Instead of Stone Age ideas like global variables, you could create a Lisp or Javascript closure, or use a C++ functino. Do monads offer a big enough advantage over those approaches to justify the endless trouble of learning them?
- kazinator 10y agoIn Lisp, you should use a global variable when you need a persistent module-level state, and let your defun forms be top-level forms: ;; bad (let ((s (make-state-thing))) (defun function () ...))) ;; good (defparameter *s* (make-state-thing)) (defun function () ...) That defparameter might be defvar: it depends on whether you want to re-initialize the state variable when a new version of this module is loaded, or keep the old value. Speaking of which, with the let, you don't get that decision. For the defun to have its effect, the form has to be evaluated. Every evaluation of the let instantiates a new binding for s in a new lexical environment, end of story. Even if we have the defparameter, we can redefine function while keeping the same state. It's also easy to inspect the state. A the REPL, just evaluate its name: there it is. You don't have to whip out a closure environment inspector. defun is a "stone age" tool anyway, which destructively overwrites a global function binding. Wrapping it with a captured lexical environment is a form of situational irony in programming (if not indeed verbal).
- pka 10y agoMaintaining state was just one example. See my other comment [0] for others. Regardless, you can't maintain state with closures in a pure language. You could close over some local "variable", but that variable would be immutable. In an impure language that would work, but then you're back to managing global state, even if that state is not defined at the top level (i.e. global in the sense that different closures could still have access to the same piece of state, concurrently). [0] https://news.ycombinator.com/item?id=13965917 https://news.ycombinator.com/item?id=13965917 EDIT: just to clarify, monads manage state purely. I.e, instead of writing: function statefulFunction(state) { (a, state1) = f(state); (b, state2) = g(a, state1); (c, state3) = h(b, state2); return (c, state3); } you can write: statefulFunction = do a <- f b <- g a c <- h b return c But the resulting function is still pure, i.e. (state) => (c, state).