
Haskell’s State monad is not a monad (2014) - setra
http://www.cse.chalmers.se/~nicsma/no-state-monad.html
======
lmm
This isn't about the state monad, it's about seq. "But now seq must
distinguish ⊥ from λx → ⊥, so they cannot be equal. But they are eta-
equivalent. We have lost eta equality!" \- once you lose eta equality you lose
everything. A whole raft of monads become impossible, not just state.

Equivalence in Haskell is defined only up to ⊥; to work in Haskell we rely on
the "Fast and Loose Reasoning is Morally Correct" result that _if_ two
expressions are equivalent-up-to-⊥. and both non-⊥ then they are equivalent.

(Or, y'know, work in Idris with --total and avoid this problem entirely).

~~~
sametmax
I always scares me how little I understand from comments talking about
Haskell. Either I'm much more dumb that I though, or the whole community
decided that purity beat practicality.

~~~
Filligree
Eta-conversion is just the statement that having function(x) { f(x) }, in JS,
is equivalent to having f. In short, wrapping a function in another function
(or removing such a layer) makes no difference.

Like most of lambda calculus, this is extremely intuitive once described that
way. Therefore, a situation for which it is false is disconcerting. It's also
considered one of the "monad laws", which is the small set of rules that
everyone assumes is true for every monad, but which the language doesn't
(can't) enforce via the type system.

⊥, pronounced bottom, is a "value" that, if you ask for it, your program
breaks. It may go into an infinite loop, or it may crash.

The GP post is therefore explaining that, because eta conversion in this case
fails when the function returns bottom, we should be disconcerted. It's a mild
form of disconcertion, however: Things would only go wonky if you try to run
code that would, in any case, hang or crash.

The reason it isn't _completely_ harmless is that the difference may be
observable: A crash in pure code triggers an exception, which can be caught in
its impure wrapper code, and a hang can in some cases be converted to an
exception by the garbage collector. In theory all bottoms are equal, but in
practice they aren't.

~~~
sametmax
Thank you.

------
carterschonwald
This is all pretty fun reading.

At least with my own code in Haskell, I work in the mental model where I
assume termination and strong normalization (all programs terminate under all
evaluation strategies).

Then, Bottom corresponds to either an exception that will get forced or a
computationally boring infinite loop (which the rts can sometime detect and
turn into an exception). My personal style favors having those errors be very
very visible.

That said, depending on what formal or informal reasoning properties you care
about (especially as a library writer), these little subtleties can be really
important!

~~~
marcosdumay
This.

This thing is not a problem on Haskell because everybody always code
defensively against bottoms. GHC is quick to teach this to novices.

It may still be a problem of "the lack of this feature inviabilises a lot of
good code". It doesn't look like it is, but nobody can be certain, so it's a
good avenue to explore.

------
danabrams
The real monad was the friends we made along the way.

------
pacala
Cute, but why should one care about ⊥, aka bottom, aka nontermination [0]?
Let's write programs that terminate...

[0] [https://wiki.haskell.org/Bottom](https://wiki.haskell.org/Bottom)

~~~
OtterCoder
It's kind of funny, looking at these halting discussions as a backend web
programmer. At the heart of it, all I do all day is _try_ to write
nonterminating programs in an environment where the entire universe seems hell
bent on terminating the event/polling loop. It's kind of perverse when you
think about it.

~~~
Zalastax
The nice thing is that you can actually build stuff like web servers in a
total language :) Using something called co-induction a total language can
model things that are potentially infinite, e.g. a web server. Co-induction
replaces the requirement that every function must terminate with the
requirement that the next step (e.g. the next value of a stream) can be
calculated in finite time. So, the event loop could be modelled using co-
induction as long as long as the event handlers do not contain infinite loops.

~~~
setr
why is that complementary instead of straightforward induction?

~~~
bollu
The style of coinductive reasoning is very different.

In general, in induction, one has a base case and an inductive case. Hence,
you can only build "finite" objects (since you start with the base case and
you can apply the induction rule a finite number of times)

In coinduction, you start with an infinite object, and you show that you can
"progress" \- that is, take a step in your computation. So, this shows that
you can _reach_ states in finite time, even if the full structure is
inaccessible, since you show progress on each state.

The phrase that I've heard is "induction is to prove what's true, coinduction
is to prove what's not false"

Please do Google "practical coinduction" it has a wealth of examples.

------
dustingetz
It seems to me a piece of software is either a formal proof or it's not. So if
it's not and we still manage to write robust programs, the natural next
question is, to what degree is it okay to knowingly violate the laws? And if
you iterate that to the nth, do you end up with Clojure?

~~~
lmm
> It seems to me a piece of software is either a formal proof or it's not.

That seems like a false dichotomy. Rather I'd say: a program carries a bundle
of proven properties with it, but for some programs this will be rich and for
others it will be more or less trivial.

> So if it's not and we still manage to write robust programs, the natural
> next question is, to what degree is it okay to knowingly violate the laws?

To what degree do we actually write robust programs though? Errors are so
common we dismiss them and retry or work around without even consciously
noticing the error. "Have you tried turning it off and on again" is funny
because it's true.

