7 ms·
Monads for Clojure Programmers
- tfwwtn 5y agorelated: https://lispcast.com/are-monads-practical/ https://lispcast.com/are-monads-practical/
- adamgordonbell 5y agoA monad, as thought of in programming, is really something that requires higher kinded types. The reason they are hard to explain in a language without HKT is the same reason why List[T] would be hard to explain in a language without generics. edit: The article is impressive. It's just it feels like a different thing to me to than what monads are in haskell. I guess it would have to be.
- elwell 5y agoI love your podcast.
- adamgordonbell 5y agoThanks!
- AzzieElbab 5y agoagreed but still impressed by authors effort
- tessierashpool 5y agoyep. as the OP said: There is a reason why most monad tutorials use Haskell: without syntactic support, monads are pretty clunky to use. (You could probaby go further and say "without semantic support.")
- mping 5y agoYou can have monads in clojure (eg: http://funcool.github.io/cats/latest/#monad http://funcool.github.io/cats/latest/#monad) but the problem I see is that they are infectious and kinda force you to use them everywhere. In other languages, it's more palatable because the compiler checks for types, and normally there is some sugar to make it easier on the eyes. Besides, I think monads are a bit off the clojure way, even if they happen to exist somehow (eg promesa and manifold libraries).
- goostavos 5y agoAnd without the compiler checking the types, they're nightmarish to use. It falls to pure human sweat to track if a variable holds a `thing` or a `Monad Thing`. In my humble experience, it leads to lots of tedious banging around in the REPL to track down where you made a mistake. This is especially painful while refactoring. I've given up on the monads in dynamic languages outside of really strict confined situations (validation for instance). I think it's ultimately a bit silly to do something like `StateT` in Clojure rather than just using an `atom` or whatever. What you lose in referential transparency and purity, you more than make up for in sanity over the long haul. The scales would tip if Clojure had stronger typing options, but for now, using the idiomatic stuff is the way to go. Although, spec could probably be used in a pinch, but it's kind've hot garbage compared to compiler checked types (imho!).
- deleted 5y ago[deleted]
- platz 5y agoIt's not that monads "require" higher kinded types, it's that , like an interface, monads only start really "buying" their "cost" when there are _many_ different things that can be monads. Then the common abstraction of the monad can be used to interact with these things that all share this common property, in a unified way. Finally, higher kinded types is what makes the idea of _many_ different things that can be monads manageable for the programmer, because if the underlying thing that the monad represents keeps changing into different forms, it's very easy to loose track of what structure or concept it currently represents without the types to keep track of that for you.
- adamgordonbell 5y agoyeah, that's better put. The usefulness of it, to me, is how the bind/flatmap can be generic over type constructors, but type constructors aren't a clojure concept. It's a static typing thing. To have an interface for monads, you need to have HKT don't you? To declare the type of bind? It could be useful otherwise. It would just seem like a slightly different thing.
- jerf 5y agoYou can also theoretically go the completely opposite direction and use dynamic typing. You have the usual tradeoffs related to that, plus maybe a couple new wrinkles, but it can work.
- fiddlerwoaroof 5y agoSomeone once told me “continuations are isomorphic to monads” and I eventually realized that the best way to do something monad-like in JS was to write functions that take a callback as their last argument and then higher order functions that manage threading the callbacks around, occasionally transforming it to slot in impure behaviors. I really disagree with the notion “HKT are necessary for monads”, though: they’re necessary to statically check that your monadic code is correct, but asside from things that rely on return-type inference, you can just implement those type-checks as runtime checks (or leave them out entirely): if the architecture of side-effects in your app is arranged around monadic principles, you can write all the same generic transformations (and, in fact, designing your dynamically typed code this way can get you a good fraction of the correctness benefits of static typechecking)
- didibus 5y agoI think Monad make more sense when you understand the context and its additional constraints as to why you'd want to use Monads. Imagine that you want to write code that is strictly pure, so that when evaluated, it doesn't do any side effect, it only returns a pure description of the computation it did. Here's a simple example: func announce-good-catch(fish) if(fish.size > 5) print "We found a big one! else print "That one won't even feed a cat, try again!" How can you make this function pure? It technically prints something, yet you want to run it and not have it print, but instead return you something that describes what branch it took and what effect it would have done had it been impure. You can do this: func announce-good-catch(fish) if(fish.size > 5) return [:print "We found a big one!] else return [:print "That one won't even feed a cat, try again!"] Now the function is pure, it doesn't print anything, it evaluates the branch to take based on input and return to you a description of what it should do. The problem is programming that way is hard, let's add another function: func get-fish(fish-id) return new Fish(db.query("select * from fish where id = " + fish-id) Again this isn't pure, so let's make it pure: func get-fish(fish-id) return [:new Fish [:query [:get :db] ["select * from fish where id = " + fish-id]]]) Already this is getting pretty complicated, getting the DB is impure so return a description of it, querying the DB is impure, so return a description of it, Fish can't be created if we don't execute the side-effect, so take note that you want to create a Fish after those two side-effects were to actually happen. Now combine get-fish and announce-good-catch(fish)? announce-good-catch((get-fish 10)) Well this works in the impure version, but this doesn't work for our pure versions because now the input to announce-good-catch isn't an actual Fish but instead it's this: [:new Fish [:query [:get :db] ["select * from fish where id = 10"]]] And so it's not going to work, you can't compose those pure functions as you'd normally would. Here is where Monads become really handy, they put all this behind an abstraction that lets you with added syntax sugar feel like your code is written in the impure style, yet under the hood it will convert things to a description of all side-effects and whatever pure behavior is pending the result of those. And it'll let you compose those descriptions together so that from the programmer point of view the code you write looks and feels almost the same as the impure version. This is the problem Monad solves. Now once you have that description, eventually you want it to actually run for real so that side-effects and all other described pending behavior gets executed. But what people realized here is you can kind of abuse this a bit, where you could choose to interpret the description in new ways, or manipulate the description prior to executing it. Basically whatever executes the description for real is in charge of how to do that and it could choose to change some of the assumptions like the evaluation order or the thread on which thing execute, etc. Thus once you have Monads, you can also use them to manipulate evaluation order and semantics. Now if you don't care about constraining yourself to pure functions, all this can seem like overkill, and it kind of is. Obviously, people will tell you it's a good idea to make everything pure, because you can more easily test things. And people will tell you, even if you don't care about the purity, how Monads let you manipulate evaluation order and semantics of the code can be cool and allow for some neat features, so maybe that's something you'd find useful, such as adding tracing throughout automatically, or automatically parallelizing things that don't depend on each other, etc. This is where some people say that Macros can often be used to similar effect. Because macros, a bit like Monads, operate on the description, macros on the code's AST itself, Monads on the captured Monadic composition. So a macro can also rewrite things to change their evaluation order or semantics. I wouldn't call myself an expert on all this yet, more of an enthusiast, but that's how I would contextualize it.
- JoelMcCracken 5y agoIMO, the best articles I know of on monads, which take two very different approaches: - http://www.jerf.org/iri/post/2958 http://www.jerf.org/iri/post/2958 - https://philipnilsson.github.io/Badness10k/escaping-hell-with-monads/ https://philipnilsson.github.io/Badness10k/escaping-hell-wit...
- sesm 5y agoThe second link is really good, thanks for sharing!
- deleted 5y ago[deleted]
- sesm 5y agoSee also https://github.com/clojure/algo.monads https://github.com/clojure/algo.monads - part of Clojure standard library which has definitions of common monads and links to tutorials
- Koshkin 5y agoWhat is described looks to me like an interpreter for a Forth-like DSL in which the notion of the monad should find its "natural" expression. Still not sure if it can indeed be viewed as a practically useful extension of the base language (Clojure)...
- sesm 5y agoI'm not an expert, but my understanding is: 1. You are writing a DSL that works with wrapped values. 2. In this DSL you can use arbitrary pure functions from your base language. 3. The value proposition is: those pure functions should not be aware of the fact that they are working with wrapped values, they are written as if they are computing regular non-wrapped values. Bind takes care of this. 4. Indeed the simplest way to implement this is some kind of interpreter. But you don't have to re-implement the entire language inside this interpreter, because of #3
- sova 5y agoCan someone give me a practical example of when one would want to use a monad? Typically I don't want side-effects to all live in one thing. It's cute but "we want to change the world," don't we?
- anchpop 5y agoTwo common examples from the Ocaml world are promises through the LWT library and options. If you just wanted to open a file, maybe you would write something like this: run(create_file("filename")) Here `create_file("filename")` is a pure value, no side effects. It's a value that represents the concept of creating `"filename"` (and maybe returning a handle). When passed to `run`, that value is inspected and only then the actual action it corresponds to, creating a file, is executed. What if you want to do something with the file? That looks like this: let create_then_delete = and_then(open_file("filename"), (handle) -> delete_file(handle)) run(create_then_delete) `create_then_delete` represents the concept of opening a file and giving it to `(handle) -> delete_file(handle)`. If you want to actually do that, you pass `create_then_delete` to `run`. Functions with a type signature similar to `and_then` turn out to be pretty common, so it's nice to have a way to talk about them. That's basically what monads are for. In OCaml, all functions that have a similar type signature can use the new "let-monadic syntax", which basically lets you rewrite and_then(open_file("filename"), (handle) -> delete_file(handle)) to let\* handle = open_file("filename"); delete_file(handle) Which is a lot more readable if you ask me. Any type that has a function like `and_then` works with this syntax, and those types called monads.
- massung 5y ago
- jibbit 5y agoAs I can’t really read Clojure (there’s a few bits there I don’t understand what’s going on), can anyone elaborate on what, if anything, is Clojure specific about the examples? Or could, say, they be just as easily written in python?
- sesm 5y agoThis example uses macros to implement monads, Python would need a different implementation.
- kccqzy 5y agoThe author is really more accurately talking about free monads. With free monads, you make the nomadic structure clear and defer the interpretation of monastic structure for later. Even in Haskell free monads are used pretty sparingly, so it's definitely not every day defining their own bind or return as data.
- dllthomas 5y ago> definitely not every day defining their own bind or return as data. I mean, thunks? But yeah, certainly not at the level we usually think of when we talk about defining data in Haskell. But I can see labeling that an implementation issue. You need to get your dictionary to the right place somehow, and lacking the automatic type-driven threading of type classes I don't know that writing an interpreter for each is the wrong choice. For a lot of monads (reader, state, etc) you want an "interpreter" anyway. It does mean that regular values can't happen to have a relevant monad that you can use to stitch them together in a way that wasn't noticed by the person who created the value.