The whole point about monads is that they provide a uniform interface for working with values that carry some extra context. The genericity of the interface is nice, in that it allows you to write generic monad combinators, but they are only useful because of the context (effects, control flow, etc). If you don't describe any of the implementations, you are missing the point!
The best introduction to monads was written in 2006 (more than eight years ago now) and I don't imagine that a better one will be written in the near future -
The point is, most times each of subsequent functions need to "see" what was in scope in the earlier functions. Most times, the later monadic actions need information that is pulled out of the monad in previous actions.
For example, consider the following:
main = putStrLn "Enter your first name" >>=
\_ -> getLine >>=
\first -> putStrLn "Enter your last name" >>=
\_ -> getLine >>=
\last -> putStrLn ("Hello " ++ first ++ " " ++ last ++ ".")
The last monadic action needs both "first" and "last" to both be in scope. This only works because of the way that it is implicitly parenthesized.
Until I "got" this, I was very confused by most of the Monad examples I found.
I wrote this up, "So You Want To Write A Monad Tutorial in Not-Haskell": http://www.jerf.org/iri/post/2928 It focuses on all the things I've seen people miss about "monads" when they try to write an implementation in another language after the third time I saw someone claim they'd implemented "monads" in Javascript using method chaining, which I do not believe to be possible in part because of the issue you point out, which is that the scope builds up as you go.
It is perfectly reasonable to also read this as the list of elements of monadery that even tutorials written in Haskell tend to miss or fail to explain.
What is your definition of "effects"? I ask because this article[0] and many others[1] seem[2] to use a contradictory definition, in which failure, nondeterminism, state etc. are viewed as effects and monads are a means of implementing those effects.
"Effect" is a more general idea. It's a lens for viewing any kind of impure computation[0]. Monads are a model for effects. You can view them as pure[1] computation in lambda calculus or as defining a region of code which, internally, has impure effects.
So to summarize: effects are a general concept, monads are a particular technology for implementing that concept.
Further complexity ensues when people start talking about the general concept of a monad which is interesting in its own right but it has a more sophisticated relationship with the concept of effects.
[0] Purity is a property of, say, functions. Its definition is a function `f` is pure if and only if
const () . f = const ()
which usually means that non-termination is impure as well. The notion of equality you use above can finesse this definition a lot.
[1] As stated in [0], non-termination is an effect, so Haskell monads are impure in that sense. Haskell typically ignores non-termination effects, though. Generally, monads would work more or less just fine even without non-termination though. Externally you can think of them as pure.
Ironically, I added that just as I was linking it here, despite the date on that post.
In addition to tel's discussion, what I was really trying to get at is that monads aren't "about" IO effects in particular, they're not about "impurity". In this case the whole thing is pure.
Defining effects at a deep programming language research level can bring a different understanding, where all monads are about effects, but "effects" has a different meaning that most people understand.
Your C# IO monad could really benefit from using LINQ (I know you mention it at the end). If you're interested in a full set of C# monads (including a fully implemented IO monad) check my csharp-monad library [0]
It's not even about the effects, really. It's simpler to think of Monad as an interface for manipulating encapsulated data in a way which keeps it encapsulated.
The simplest way to encapsulate data is to wrap it using a constructor which we keep private to our module:
module M1 (Encap(), val1, val2)
data Encap a = mkEncap a
val1 :: Encap Int
val1 = mkEncap 5
val2 :: Encap Int
val2 = mkEncap 10
Other modules importing M1 get access to the Encap type, val1 and val2 but not the mkEncap constructor. They can use val1 and val2 as-is, but they can't construct new Encap values or destruct existing Encap values to get at their contents. The problem is, there's not much we can do with this interface.
One way we can make this more useful is being able to apply some function to an encapsulated value. That's what Functor is for, so we can add this to M1:
instance Functor Encap where
fmap f (mkEncap x) = mkEncap (f x)
Now users of M1 can transform encapsulated values without being able to break the encapsulation. For example:
val1Plus7 :: Encap Int
val1Plus7 = fmap (+7) val1
val2Str :: Encap String
val2Str = fmap show val2
We're still pretty limited though, since there's no way to combine encapsulated values into new encapsulated values, or to encapsulate our own values. That's what Applicative provides, by letting us construct encapsulated values without gaining the ability to destruct them, and by allowing encapsulated functions to be applied to encapsulated values (since functions are closures, this lets us gather up encapsulated values and combine them arbitrarily):
instance Applicative Encap where
pure x = mkEncap x
(mkEncap f) <*> (mkEncap x) = mkEncap (f x)
Now users of M1 can encapsulate and combine values, like this:
-- val1 + val2
val1PlusVal2 :: Encap Int
val1PlusVal2 = fmap (+) val1 <*> val2
-- New encapsulated string
val3 :: Encap String
val3 = pure "Hello world"
-- val3 repeated val1 times
val3Repeated :: Encap String
val3Repeated = fmap rep val1 <*> val3
where rep n _ | n <= 0 = ""
rep n s = s ++ rep (n-1) s
This is quite a powerful interface, but one thing we can't do is `collapse` double-encapsulated values into single-encapsulated values. That's what Monad provides:
join :: Encap (Encap a) -> Encap a
join (mkEncap (mkEncap x)) = mkEncap x
An alternative, but equivalent, definition is to allow calls to encapsulation-producing functions without encapsulating their result. Haskell's Monad is defined this way:
instance Monad Encap where
(>>=) :: Encap a -> (a -> Encap b) -> Encap b
(mkEncap x) >>= f = f x
These kind of encapsulated values turns out to hold effects without breaking the language, and these interfaces turn out to be powerful enough for general computation.
>These kind of encapsulated values turns out to hold effects without breaking the language
I wonder if you're hand-waving a bit here. To me "encapsulated values" describes Identity or Maybe, but really doesn't work (IMO) for e.g. State or IO where bind is composition.
I would use the term "wrapped-up" where you've used "encapsulated", and in fact I specifically avoided talking about wrappers for this exact reason :)
Maybe my terminology could have been better, but I meant "encapsulated" in analogy to OOP, which advocates "encapsulating" all data via methods. The OOP definition of encapsulation includes using getters/setters, which is like having "wrapped-up" properties, but the idea is that we can go beyond this to calculate the data in arbitrary ways without our clients having to know about the implementation. That's what I was trying to get at here; for example, the implementation of IO involves horrible imperative yukiness, but we (the client) don't need to know that: we just use the interface, and if our functions ever get called, they will be given an appropriate argument.
I agree. I read that comment last night and could almost not sleep due to the insights it gave me. I'm going to spend the whole weekend re-reading about Functors, Applicative and Monads now. Maybe this time I'll grok it all.
The best introduction to monads was written in 2006 (more than eight years ago now) and I don't imagine that a better one will be written in the near future -
http://blog.sigfpe.com/2006/08/you-could-have-invented-monad...