35 ms·
Monads and Intensionality – Lucid is not an aberration
- greydius 6y agoIt's unfortunate that so many people in the software profession have a negative attitude towards these concepts. Despite its unfortunately alien name, monad is a great abstraction for patterns that come up often in this field. > So what is the IO monad, the most famous of them all? IO is the State monad where the state is the entire universe.
- lmm 6y ago> IO is the State monad where the state is the entire universe. Maybe in the early days, but that doesn't really describe how it works these days (in particular, async exceptions). In practice it's been used as a "sin bin" type for any side effect that we don't know how to model nicely.
- codebje 6y agoAre you referring to the argument that Haskell's IO type is insufficiently granular to distinguish between something like erasing a disk and something like catching an async exception, or that the language's first-class feature set should be extended to things like async exceptions so they don't need to be part of "the rest of the universe" ?
- lmm 6y agoI'm saying that if you think of IO as being a state monad then you will be surprised by the behaviour of async exceptions (and likely introduce bugs in your program). I'm not taking a view on what Haskell should do, just saying that it's something people using the current IO type need to be aware of.
- ezrast 6y agoI don't have a negative attitude towards the concept of monads nor one informed by its funny name (where did you even get that notion? Programming is filled with jargon). I do have a bit of a negative attitude towards evangelists who expect people to be impressed by ideas whose significance they routinely fail to communicate. Functional programming enthusiasts seem to really want the rest of us to care about monads but never quite get around to telling us why, and that's on them. I get that, say, Result types and List types both can wrap objects and both compose with themselves in some nice ways when they do. And as a mathematically curious person, I think this is a fun observation. But since there is very little overlap in their practical use cases I don't understand why having a formal model for that commonality is so important. Since you said it comes up often, do you have an example of a time where you reached a solution faster by recognizing that it should be monad-shaped?
- lmm 6y ago* Tracking which things need to happen in a database transaction * Gathering statistics (multiple different cases) * Authentication * Async pipelines (I found iteratees much easier to learn than "reactive streams" because they're just monads) Essentially any time you find yourself with a "cross-cutting concern", something you'd be tempted to use an "aspect" or "decorator" for, you probably want to use a monad. And there's a lot of complicated language features that you can just remove (or reduce to syntax sugar) if your language has monads instead: https://philipnilsson.github.io/Badness10k/escaping-hell-with-monads/ https://philipnilsson.github.io/Badness10k/escaping-hell-wit...
- tsimionescu 6y agoWell, this shows one simplistic partial syntactic solution to a few common problems. Partial because it does not handle errors in the continuation example, and simplistic because it only works at the function level, it's not clear how this would look like when 'distributed' through a large code base, like cross-cutting concerns typically are. Not to mention, it's not clear how to compose all of these separate solutions - how will the do notation work if you have a list of continuations that can each return optional values that return errors if something is not authenticated? Note, I'm not claiming that these problems are not solved by monads. I'm arguing that the article you showed gives me no information on how really complicated problems are actually solved, it just shows a neat bit of syntax sugars that works on a few toy problems (again, that's what's shown, not claiming that this is all that 'do' is).
- kstenerud 6y agoWe don't have a negative attitude towards these concepts; we just don't have a damn clue what these concepts even are, or how they're useful. And thus far the FP crowd haven't been very effective in communicating this. I'm still waiting for THE article that actually explains how this stuff works in an accessible way, without resorting to a bunch of haskell code (for which you need to already know FP - catch-22), an assumed proficiency in category theory, heavy maths, esoteric toy problems that don't touch the real world, or technically correct yet unapproachable jargon ("A monad is just a monoid in the category of endofunctors, what's the problem?"). Could you imagine teaching BASIC with "A variable is a container for values, which are themselves elements of a set of constrained possibilities. You assign to variables by collapsing the set into one possibility." That's what most FP articles feel like. To have success in communication, you need to speak to your audience where they are; not where you wish they'd be. Feynman was able to do this for physics; hopefully someday someone will do it for FP, and then you'll find us MUCH more receptive to the message.
- lmm 6y agoThis is pretty much the standard definition of monads, just everything is being talked about in terms of domains and sets rather than functions and values. Maybe that's clearer to some people; I find it less clear than talking about what a monad does pointwise. > This is my best shot. What I don’t understand is where the side effect comes in. The other monads I’ve discussed don’t produce side effects. This is putting the cart before the horse. The goal is to have a sensible way of modelling computations that have side effects, to be able to talk even slightly denotationally about (side-effecting) computations. An element of D* is a side-effecting computation that produces a value that's an element of D; this isn't something that comes out of the monad model but something we put into it, because the point of the exercise is to talk about side-effecting computations that produce values. The point is that such computations are monads in a very natural way: d* is the value d (or, equivalently - in a very concrete sense in Haskell, since it's a lazy language - a computation without side effects that produces the value d), f* is composition with the pure computation f (so f* (x) has the same side effects as x: it's the computation that consists of executing x and then applying f to its result). The sum of two elements in D* x + y is executing x, then executing y, then summing their results (and thus its side effects are the side effects of x followed by the side effects of y), and the collapse from D* * to D* is that we execute a computation and then execute the resulting computation. The advantage of this is the same as the advantage of modelling anything else as a monad: we get a formal representation of side-effecting computations that has some useful algebraic properties. The monad is a means for working with the effect we want to work with, whether that effect is partiality, nondeterminism, streaming, or general side effects. We shouldn't be surprised that side effects come out of using a monadic model of side effects, any more than we should be surprised that nondeterminism comes out of using a monadic model of nondeterminism.
- eli_gottlieb 6y agoWouldn't infinite streams more naturally form a comonad than a monad, though? They're coinductive types.
- lmm 6y agoDepends how you're using them. Apparently Lucid finds this "diagonal" approach to streams - which is not the normal way of streams in most programming languages - to be useful, in which case the monad model is a good fit.
- deleted 6y ago[deleted]
- bollu 6y agoI find this entire discussion to be indicative of a deeper disconnect within "programming culture" [insofar as such a thing exists]. Broadly, it seems to me there are are (a) folks who value mathematics and the ability to reason about programs using mathematics, and (b) folks who do not see the value of being able to reason about programs. I personally fall into the (a) camp, since I work on compilers and formal verification. Monads are mathematically useful when trying to define the semantics of a programming language. This mathematical usefulness translates into useful API design [which I personally see as the hallmark of all functional programming techniques]. On the other hand, if one does not care about or want to abstract over a very generic notion of a side-effect, yes, monads are useless. You can go your entire life without needing to know what one is. And that's okay. Why does side (a) side feel the need to pressure side (b) into learning all of their mathematical tools? Why does side (b) see side (a) as "being difficult" or "being obtuse on purpose"? As far as I can tell, the two groups do not even share the same axioms about how we should build and reason about programs. Monads are a red herring. - Real world example of where monads are used to reason about programs: Inside the VE-LLVM codebase, which provides a formal model of LLVM in Coq to prove certain properties of certain algorithms used with LLVM corred, a monad is used: https://github.com/vellvm/vellvm-legacy/blob/e4c22d795974ba7c768c18b74fa098b0be2f86f7/src/Vellvm/monad.v https://github.com/vellvm/vellvm-legacy/blob/e4c22d795974ba7.... Much of the reasoning around sequential side-effecting semantics is phrased in this language.
- kstenerud 6y agoWe don't see the value in it because as of yet nobody has explained what the value is in an accessible way. That link, for example, is completely incomprehensible. Had you not mentioned what it's for, I couldn't even venture to guess as to what it does. Even with a description, I still don't know what it actually does, why it's structured that way, or what value there is in it. From our side of the fence, FP concepts appear dense and obtuse the way they're currently explained. Until that changes, we'll remain divided.
- asgard1024 6y agoI think what you have to understand is that monad is quite an abstract concept. It is possible to give a specific example of a monad, but from the specific example, you won't fully understand it. Here's the first sentence in the documentation for Java's Comparable interface: "This interface imposes a total ordering on the objects of each class that implements it." This assumes people know what a total ordering is. Total ordering is an abstract mathematical concept, not really more or less abstract than a monad. Clearly then, people don't have problems grasping abstract concepts. They just learn the definition and possibly bunch of examples and they're done. I think the real divide happens because people in programming praxis are simply skeptical to the claim that monads are a useful abstract concept to learn and use in programming. Many years ago, some of them probably thought they don't need to know what a total ordering is. I don't think anything can be done with the skepticism other than either take the claim at a face value, and accept that monads are a useful concept, or verify that claim by learning Haskell for instance.
- codebje 6y agoNice read. I'm not familiar with Lucid, so invocations of "fby" and so forth went over my head, but finding that there's an abstraction available to encapsulate and explain a lot of behaviour in a consistent way is always a good win. The "output monad" described is usually called the Writer monad - you can append to a log, but you can't do anything based on what was in it. It can be generalised from strings to any monoid - a domain that has an empty value and an associative concatenation function. The IO monad, as another commenter said, is a kind of state monad where the state is something ineffable from within the computing environment: it's the state of the world outside the program. But there's no need to rush to trying to fit the IO monad to this model - we can start with the "reader monad". An element of D* for the reader monad is a function from an element of some specified domain R to an element of D. The mapping from D to D* is a constant function that produces D no matter what the argument to the function in D* is. D's elements are functions from R to D* so the collapse is function composition and therefore associative. Likewise, f* from D* to E* is function composition, which composes, obeys the embedding of D into D* and since both collapse and embedding functions is function composition f* * (s')V == f* (s'V). The reader monad is sometimes called the environment monad, because there's some environment (the value from R) that is available to all computation. The key thing it gives us for this article's model of monads is that elements of D* don't have to just be pairs, they can be functions. Sort of combining the Reader and Writer monads gives us a monad where an element of D* is a function from an element of the domain S to a pair from (D, S). The embedding from D to D* is the function that copies its input state to its output pair. The collapse from D is composition with application. An element of D* * is a function S -> (D* , S), or S -> (S -> (D, S), S); apply the function in the pair to the state in the pair and you get S -> (D, S). D* * * is the further nesting of this structure, S -> (S -> (S -> (D, S), S), S). Evaluating this is associative: either way you will pass the initial state through each function in the same order to get the final output state. Embedding a function also satisfies the laws; effectively it will transform the final output D and leave the state S alone. The collapsing of State to ensure its functions are always evaluated in the same order is how the IO monad provides sequentiality of effects. The state is "everything outside the program" - the user, the keyboard, the network, the operating system, even parts of the interpreter state that are not typically open to program inspection, perhaps allocations or bit-level representations. An element of D* for the IO monad can read from the state of "everything outside", seeing whether the operating system has a character in its console input buffer perhaps. It can modify the state of "everything outside" by writing a character to the OS console buffer. And it preserves the order of these things because of how collapse is defined. It also needs a magic interpreter that can provide the initial state of the world and maintain it as the program updates that state, along with magic primitives that can make changes to an ineffable "real world" state.