4 ms·
What you write is technically correct for a sufficiently relaxed definition of the word "monad", but omits a very important point: when something of type Intege
by hollerith 8y ago
What you write is technically correct for a sufficiently relaxed definition of the word "monad", but omits a very important point: when something of type Integer is evaluated in a Haskell program, the evaluation is guaranteed not to have side effects. The language and the standard library have been carefully arranged so that that is the case. If you want the evaluation to have a side effect, you have to change its type to `IO Integer`. Kiselyov's approach to monads in Scheme in contrast make no such guarantee and cannot be modified to make the guarantee. The situation with monads in Haskell is analogous to the situation around data hiding in certain "pure" object-oriented languages which have been carefully arranged so that the only way to mutate the state inside an object is to send a message to the object. Both guarantees make it easier to reason about programs. (Another example of a language that has been carefully arranged to make a guarantee is how Rust guarantees against use-after-free errors.)
That is really all I have to say. The rest of this comment is designed only to prevent my being dismissed by a reader because what I wrote above is not precise enough.
When I referred above to monads in Haskell it would have been more precise for me to write "the IO monad and if you are into monad transformers, user-defined monads with the IO monad in its implementation". Monads have multiple uses in Haskell. (e.g., the List monad is involved in the implementation of list comprehensions.) It is only the IO monad (and maybe the relatively obscure ST monad, but every use of the ST monad can be transformed into a use of the IO monad with some loss of conciseness and maybe readability) that is involved in the guarantee about side effects.
When Haskell and its standard library were being carefully arranged, the designers had to make some choices as to what is considered a side effect and consequently what is in the IO type. For example, a loop does not have to have `IO` in its type signature in Haskell, so looping endlessly is not considered a side effect in Haskell even though maybe it should be, and in fact it is possible to define a language similar to Haskell where a loop must have `IO` in its type signature: specifically, you get rid of tail recursion with the result that the only way to write a loop is to use the fixed-point operator (i.e., the Y combinator, which since you got rid of tail recursion you have to provide as a primitive of the language) and then instead of giving Y the type (a -> a) -> a you give it the type (IO a -> IO a) -> IO a. There is some ambiguousness in other words in what is considered a side effect, but that ambiguity does not undermine my point because any skilled definition of side effect in Haskell will provide some guarantee.
- YorkshireSeason 8y agoThanks. Very interesting! I agree monads can be understood in multiple ways. But so can side-effects -- as you point out yourself ("designers had to make some choices as to what is considered a side effect"). Why should Haskell's definition of side-effect be the be-all-and-end-all of analysis on what counts as an effect? I do think that Haskell's choice of what counts as an effect is wrong, both from a theoretical POV and pragmatically. I'm happy to defend this claim, but HN is probably not the right venue for this. (Just two hints: (α) if you think about types from the POV of guaranteeing linearity by types, then non-termination is clearly an effect, and not one that should be IO; (β) Scala's approach to monads doesn't guarantee effect freedom, yet is widely used.) I consider the (co-)monad abstraction to be orthogonal to effect definitions, with the purpose of (co-)monads being to allow abstracting out certain boilerplate do to with composition.