
Monads and Intensionality – Lucid is not an aberration - rutenspitz
https://billwadge.wordpress.com/2020/08/04/monads-and-intensionality-lucid-is-not-an-abberation/
======
lmm
This 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.

~~~
leephillips
[https://news.ycombinator.com/formatdoc](https://news.ycombinator.com/formatdoc)

~~~
lmm
Thanks, have added spaces after the *s.

------
bollu
I 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/e4c22d795974ba7...](https://github.com/vellvm/vellvm-
legacy/blob/e4c22d795974ba7c768c18b74fa098b0be2f86f7/src/Vellvm/monad.v). Much
of the reasoning around sequential side-effecting semantics is phrased in this
language.

~~~
kstenerud
We 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
I 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.

~~~
kstenerud
Therein lies the problem. Until a Feynman with the skill to teach this in an
accessible way takes on the subject, we'll remain at an impasse. This is
really a UX problem.

"If you can't explain it simply, you don't know it well enough"

\- Albert Einstein

------
greydius
It'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.

~~~
ezrast
I 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
* 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-
wit...](https://philipnilsson.github.io/Badness10k/escaping-hell-with-monads/)

~~~
ezrast
Thanks, those are good examples. The linked article underscores my point
though: the author takes four disparate ideas and reduces them to the exact
same code in each case. All that code tells me is that there are monads going
on; that's no good to me if I can't tell what those monads are actually doing!

Also, for what it's worth you can get rid of a lot of those same language
features with good old OO methods. See Crystal's each[1] and try[2] methods,
for example.

I don't mean this to be a tit-for-tat and do appreciate the response. You've
inspired me to try and make use of a state monad in some Ocaml I've been
playing with. Cheers.

[1][https://crystal-
lang.org/api/0.35.1/Indexable.html#each(&)-i...](https://crystal-
lang.org/api/0.35.1/Indexable.html#each\(&\)-instance-method)
[2][https://crystal-
lang.org/api/0.35.1/Object.html#try(&)-insta...](https://crystal-
lang.org/api/0.35.1/Object.html#try\(&\)-instance-method)

~~~
lmm
> Thanks, those are good examples. The linked article underscores my point
> though: the author takes four disparate ideas and reduces them to the exact
> same code in each case. All that code tells me is that there are monads
> going on; that's no good to me if I can't tell what those monads are
> actually doing!

This is like saying that a generic list type makes no sense because you can't
tell what the values in the list actually are. The whole point of using monads
is so that you can separate the generic structure of what you're doing
(composing together effectful functions) from the specific details of what
those effects are, and there are a bunch of useful generic functions that you
can write (analogy: sorting the list, or combining together two lists) and
reuse for any kind of effect.

> Also, for what it's worth you can get rid of a lot of those same language
> features with good old OO methods. See Crystal's each[1] and try[2] methods,
> for example.

Sure. I think if you actually really follow through on OO principles like
SOLID and separation of concerns then you end up in much the same place as
functional programming. If you replace that "each" and "try" with versions
that return a "transformed" value (more composable and testable than just
executing a function for side effects) then you more-or-less have the "map"
function.

(Unfortunately most languages aren't sophisticated enough to let you pull out
that common interface: I'm sure you can see that a function List<Int> ->
List<String> has something in common with a function Set<Int> -> Set<String>
or a function Future<Int> -> Future<String>, but programming languages that
let you express what that common part is are surprisingly rare).

~~~
ezrast
I'm not saying the type makes no sense, I'm saying the article doesn't
communicate any value to people who aren't sold on the idea of monads before
they go in. Like, if you're explaining functions to a newbie programmer, you
don't just take a bunch of lines of code, replace them with
`do_the_function()`, and act like you've made the program better. Obviously
all those lines are still hiding in the function definition somewhere and your
indirection has only increased complexity. You have to show that using
functions to extract patterns from around the program improves elegance or
expressiveness overall (e.g. by reducing total lines of code). I've never read
a monad tutorial that does this in a way that (comparatively) simple higher-
order functions wouldn't do just as well.

edit: to expand on that last sentence, the example under "Ad hoc solution:
Promises" is more expressive to me than the author's monadic code, because a)
it's more explicit about what depends on what, and b) the ".then" method name
gives you a hint about what sort of computation is actually going on.
Replacing "then" with "try" or "each" makes the code work just as well for the
null-checking and for-loop examples.

~~~
lmm
> I'm not saying the type makes no sense, I'm saying the article doesn't
> communicate any value to people who aren't sold on the idea of monads before
> they go in. Like, if you're explaining functions to a newbie programmer, you
> don't just take a bunch of lines of code, replace them with
> `do_the_function()`, and act like you've made the program better. Obviously
> all those lines are still hiding in the function definition somewhere and
> your indirection has only increased complexity.

I'm not convinced. Imagine a tutorial for the foreach construct: it would show
code for iterating over a list, code for iterating over a set, and code for
iterating over an array, and then a foreach loop. Would you say that obviously
the iteration code is still hiding somewhere and your indirection has only
increased complexity? Introducing a common interface - which is proven by the
use of a common syntax - is something that we recognise as valuable, I think.

> You have to show that using functions to extract patterns from around the
> program improves elegance or expressiveness overall (e.g. by reducing total
> lines of code). I've never read a monad tutorial that does this in a way
> that (comparatively) simple higher-order functions wouldn't do just as well.

FWIW my own effort is [https://m50d.github.io/2013/01/16/generic-
contexts](https://m50d.github.io/2013/01/16/generic-contexts)

~~~
ezrast
An interface is a kind of relation between things that would otherwise be
separate. So yes, I'd say that establishing the interface by itself increases
complexity slightly.

However, there are lots of situations where one may want to substitute one
enumerable type for another, and thus lots of opportunities to recoup that
complexity. The first time the interface lets me write one function instead of
two (say, a batch processing function that can deal with objects in an Array
or those being streamed from an IO, because they both implement foreach),
that's a win.

I have to think a lot harder to come up with a situation where I have an
object that might be an Option and might be a Future, and I don't care which
type I have. It doesn't seem like it would ever come up in business logic, so
we're probably looking at some sort of mid-level abstraction that depends a
lot on the code on both sides of it. Maybe that's why writing a decent,
concise tutorial is so hard.

I like your article. You make explicit that being able to use Options and
Futures interchangeably is the objective, and your example code actually
depends on all the monad behaviors instead of just flatMap (I think - Scala is
a foreign language to me). I still don't know how often those circumstances
come up in practice, but maybe with experience I'll start seeing the patterns
more often. Thanks again.

~~~
lmm
> However, there are lots of situations where one may want to substitute one
> enumerable type for another, and thus lots of opportunities to recoup that
> complexity. The first time the interface lets me write one function instead
> of two (say, a batch processing function that can deal with objects in an
> Array or those being streamed from an IO, because they both implement
> foreach), that's a win.

> I have to think a lot harder to come up with a situation where I have an
> object that might be an Option and might be a Future, and I don't care which
> type I have. It doesn't seem like it would ever come up in business logic,
> so we're probably looking at some sort of mid-level abstraction that depends
> a lot on the code on both sides of it. Maybe that's why writing a decent,
> concise tutorial is so hard.

I see where you're coming from. Monads are parametricity rather than
substitutability: an Option and a Future are similar to each other in the same
sense that a List<Int> and a List<String> are similar to each other. You'd
never have a situation where you didn't care if you had a List<Int> or a
List<String>, but there are still functions like sort() or find() or foreach()
that are useful at the generic level.

And it's hard to do a good concise monad tutorial because they're an
abstraction over an abstraction. Really a good tutorial would probably need
three different examples - say Option, Future, and Writer - but then you'd
probably need three examples for each of those to show why they're a
worthwhile thing to be using in the first place.

> your example code actually depends on all the monad behaviors instead of
> just flatMap (I think - Scala is a foreign language to me).

flatMap/bind is the most important part - if you have a well-behaved (i.e.
associative) flatMap you're 90% of the way to being a monad. I did use "wrap"
for the base case (empty list), which is the kind of place it usually comes
up. Don't get me wrong, it is important, but you can go a long way with just
flatMap.

> I still don't know how often those circumstances come up in practice, but
> maybe with experience I'll start seeing the patterns more often.

Once you're used to them you see them everywhere - it's such a simple and
general interface. I said anywhere you'd have a cross-cutting concern; another
heuristic might be anywhere that you'd like to use the command pattern if you
were "doing it properly", but there would be so much overhead to actually
defining a command type. Simple things like authorisation -
[http://blog.sigfpe.com/2007/04/homeland-security-threat-
leve...](http://blog.sigfpe.com/2007/04/homeland-security-threat-level-
monad.html) is slightly a joke, but it's something you can apply genuinely in
an application that has some concept of being logged in at different privilege
levels.

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

