5 ms·
monads if the language is functional Monads are not a functional concept. For example an imperative language like Scala (which, being in the ML family of lang
by YorkshireSeason 8y ago
monads if the language is functional
Monads are not a functional concept. For example an imperative language like Scala (which, being in the ML family of languages has a powerful and first-class functional sublanguage) has monads that work perfectly fine with imperative computation. Indeed, I find it a good programming style to let
trivial, ubiquitous monads, like printing / IO / global state, be handled by the built-in primitives, while higher-level, unusual, application-dependent effects are implemented explicitly using monads.
Monads are essentially a form of overloading / re-defining composition operators, e.g. the semicolon operator in traditional imperative languages. In languages like Haskell and Scala, types (and higher kinds) are used for this purpose, hence it is often though that monadic programming is restricted to statically typed languages with powerful types, and don't make sense for dynamically typed languages. Nothing could be further from the truth. All we need is a 'hook' into the sequencing mechanism(s) the language provides, at least in principle. Admittedly, the outcome is not nearly as pretty as in statically typed languages. See [1, 2] for discussions.
[1] T. Garnock-Jones, Monads in Dynamically-Typed Languages. https://eighty-twenty.org/2015/01/25/monads-in-dynamically-typed-languages https://eighty-twenty.org/2015/01/25/monads-in-dynamically-t...
[2] O. Kiselyov, Monadic Programming in Scheme. http://okmij.org/ftp/Scheme/monad-in-Scheme.html http://okmij.org/ftp/Scheme/monad-in-Scheme.html
- hollerith 8y agoWhat 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.