Karl von Laudermann wrote:
I certainly don't understand the monad. I read some introductory
material re: Haskell just a couple of weeks ago, out of curiosity about
the language. My understanding of the monad concept and Haskell's use
thereof is basically this:
- Haskell is a purely functional language
- Purely functional languages cannot have side effects
- A language that can't do I/O is useless, so we need a way to put side
effects into a purely functional language
Therefore:
- We'll put I/O subroutines into Haskell, *but* we'll call them
"monads", and that magically fixes the problem somehow
That's not quite correct. Monads are not an ad-hoc concept. They are a mathematical concept that existed *before* Haskell was even invented. They have their source in category theory, which is as remote from programming as any mathematical concept can be .
There have already been good replies, but I thought I might add my two cents here. Note that it's how I understand monads. It may not be mathematically correct, but it seems to fit their behaviour.
The thing about monads, and the IO monad in particular, is that they aren't functions on objects: they are datatypes, or rather parametric datatypes, i.e. functions that work *on types*. IO, for instance, is a function that takes a type a, and returns a new type called "IO a". So you can for instance take a String object, apply an IO operation to it, and that will return a new object of type "IO String". And note that this operation doesn't have a side-effect. It's purely functional. It's rather that when the IO String object will be evaluated, the evaluation will result in printing the string on the output. That is not a side-effect, because evaluating a variable isn't an operation, but the effect of executing a program.
In other words, a variable of type "IO something" encapsulates an IO action, and the IO subroutines don't have side effects by themselves, but rather return IO actions as their result. And it's the IO action itself, when evaluated by the Haskell interpreter/runtime (because it needs the value of that variable to carry on its computation, like it could need the value contained in a variable of type Integer) that will result in some interaction with the outside world. This way, the sides effects are contained in variables, which can only be of an IO type.
But there's more to it. Being a pure, functional and lazy language, Haskell doesn't have an order of execution. But IO actions need sometimes to be ordered. Luckily, by their very mathematical definitions, monads have to follow some axioms, which define operations on those monads. One of those operations allows to define a concatenation operator, which in the case of IO takes two IO actions and returns a single IO action, which when evaluated will result in the two IO actions to be evaluated in order. This way, you can ensure that IO operations happen in the correct order, and to hide the mathematical aspect of this operator called >> Haskell provides convenient syntactic sugar (in the form of a do... end block) to write concatenations of IO actions in a way close to imperative language.
But monads aren't used only with IO. Another monad is the parametric List, which if I'm not completely mistaken encapsulates linked lists. In this case, the concatenation operator that was used with IO objects to evaluate them orderly can be used with Lists to concatenate two Lists into one.
Monads are quite an esoteric feature, and my explanations probably don't do them justice. But you can be sure they aren't just an ad-hoc construction to pretend you don't have side effects in IO. They are a deep mathematical concept that by chance happened to have the right operations to be used in Haskell IO.
As for applying something like monads to Ruby... Well, I don't know whether this would be really necessary. The reasons why monads are useful in Haskell (encapsulation of side effects, orderly evaluation, collections, etc...) can be handled pretty well as they are. Moreover, to implement them in the way Haskell does, the monad would probably have to be defined as an operation transforming a class into another class (in the case of IO, a class would be transformed into another class which would have the peculiarity that when an object of that class is evaluated by the Ruby interpreter, it results in some input or output interaction). It would be an interesting exercise (hey, I might look into it just for fun), but I'm afraid it would be rather academic, but fun .
···
--
Christophe Grandsire.
http://rainbow.conlang.free.fr
You need a straight mind to invent a twisted conlang.