I think I see what you mean. However, it is the other way around: some monads are special cases in the way that they have extra methods that allow you to "escape" their context. Like lists: you can fold over them and get out a different result type.
But in general you can't do that with monads, in particular not if you just have "any" monad at hand but you don't know which one it is. And IO is one example, parsers would be another one.
The important missing context is that a monad has `(return, bind)`, while a comonad has `(extract, extend)`, and both have their own respective laws letting you reason about combinations of those operations.
You can certainly do what you describe if you have a monad, but since you can't even try to unwrap a value if you don't know what the monad is concretely, you have no way to write a function `T a -> b` that's generic over T. Functions `T a -> b` only really make sense for comonads, which give you the `extract` and `extend` functions as primitive.
I used to feel this way too, so I'm not trying to be coy, but really that's a property of a (some) Functor(s), not a Monad (except in that all Monads are also Functors).
Monad is just the pattern where you combine Functors. Some Functors can be pattern matched upon to "escape" their values. Functors like IO cannot. IO would still be (academically) useful if not for its Monad instance... you'd just only be allowed a single effect in the entire program!
Imagine you have a context (the monad), which contains a value of type `a`...
> "by hiding the extraction of the value contained in the monad...
Means, you don't need to care about how we get the value out of the context, it will just be done for you. That's part of the monad's job. Each different type of monad may do it differently (a List will do it many times, an Option will do it zero or one times, Async promises to give you a value, etc. ).
> "... and passing that value to a user supplied lambda that takes a value of the type of the value contained in the monad ..."
So, we have a function called `bind`, which is part of the interface for monads ... it takes the original context (monad) as well as a lambda as arguments.
Below is the signature: `m a` which is the original monad value, (a ? m b) is the lambda, and `m b` is the return value, which is a monad with its contextual value type changed from `a` to `b`
bind :: m a ? (a ? m b) ? m b
Where you see `m a` and `m b`, they're generic types which are parameterisable on both the inner and outer type, the inner type `a` is fairly usual, but the outer-type `m` is a higher-kind and can also be parameterised, `m a` could become `List a` (which is like List<A> in C# or Java).
So, if you squint, you might be able to see that the `a` comes from within the `m a` context, gets passed to the lambda, which needs an `a` argument and returns an `m b`, then that `m b` is returned as the final result.
Each type of monad has its own implementation of `bind`. So, for a List the bind operation looks like this:
bind :: List a ? (a ? List b) ? List b
And so you should see that for Lists each `a` will be extracted from the first argument (so we'll have zero or many `a` values), each `a` is passed to the lambda which will return a `List b` (and so we have zero or many `List b` values), and those lists of `b` will be concatenated to give a flattened result of `List b`. And so, that's the behaviour if the List monad. Each monad will have its own rules, but must follow the signature of `bind`.
> "...and returning a new monad containing a different type..."
This just means the `a` turns to a `b` (but the `m` stays the same). Essentially it means you can do some kind of mapping of the contextual value(s) to return a new type. So, if you were to map Ints to Strings the bind signature would look like so:
bind :: List Int ? (Int ? List String) ? List String
In practice you can see how the lambdas returning `m b` allow for chaining of computations. The code below visits the items in two lists (listA and listB) and sums them. The lambdas close over each other to create a context for the `return` at the end of the process. Which is very close to how scoping works in imperative languages.
bind listA (\a ?
bind listB (\b ?
return (a + b)))
The key to any monad is its bind function, that's where its behaviour is. But fundamentally, it's a contract, or interface which forces ordering of computation... the values returned by the lambda (which are monads themselves), clearly can't be evaluated until you evaluate the first argument to get the `a`.
And so, `m a` must run before `m b` ... combine that with `do` notation and you'll get something that looks and feels a lot like imperative code, but all of the side-effects are encapsulated and declared, and all of the logic is pure (referentially transparent).
The example above with `do` notation is:
a ? listA
b ? listB
return (a + b)
Which is essentially equivalent to a nested for loop without the ceremony.
It seems you're talking about the IO monad specifically, not about monads in general. Other monads, like Maybe and List, absolutely occur in programs written by "most developers in other languages", whether they perceive them as monads or not.
For instance, a pointer type in C is like a "Maybe a" in Haskell, because a pointer can always be null (Nothing).
Put another way, use each monad instance on its own, and ignore that it's a monad in the first place (ie. use the IO monad, but pretend the syntax is IO specific, use the List monad, but pretend the syntax is List specific etc.)
After you do enough of these, you'll start to see the common pattern, and wish there was a way to abstract over them. Surprise: there is!
This is basically I assume monads were developed as well.
Sure, but that's not a feature of Monads themselves, but a feature of the excellent generic programming support in Haskell, along with polymorphic return types that makes the boilerplate really easy.
Still, there is a slight bit more to the story if you actually had to pass a [c] to another function ( you would have to liftM that function as well, I believe, and so on for every function in a chain).
Monads are so pervasive that multiple languages have determined that they deserve special syntax (see: Haskell's do-notation, OCaml's let-syntax). Those syntaxes play nicely with a ton of seemingly-different types, such as promises, options (maybes/nullables), lists, monadic error handling (either, or_error), etc.
On a similar note, recognizing the structure of monads has in the past allowed me to abstract things in an interesting way. For example, say I have a dict lookup function (takes a key, returns a value) and I want to build a bunch of useful functions on top of that. But we want to use them in many different contexts - the dict might be remote or locally in memory (promises vs. not) and we may or may not be in a test context (monadic vs exception-based error-handling). By recognizing that we can just abstract over monads, we can easily handle these 4 cases (promise, promise+error monad, error monad, unit monad).
If I remember correctly, there was a more tricky problem in implementing monads without the type system - sometimes which monad is chosen for an expression, depends not just on the arguments, but the expected type of the result. Ex : "return 3" might be [3] or an IO action with result 3. The compiler decides this based on the surrounding context. In dynamical languages one would have to manually specify which kind of 'return' you mean.
This surrounding context inference feature, which is useful for type classes other than monads, has the unusual consequence that you need to specify more typeinfo in a dynamic language.
You're probably use to Monads that wrap a single object, like the maybe monad. Monads can wrap multiple objects or anything. This is the case for a list.
Sure, but to me the best things about monads are precisely those ‘other uses’. And if that means you still need monads, then why not use them for IO as well? That then gives you the advantage of unifying everything under the same abstraction.
No, you deeply misunderstand what a monad is. Monad doesn't mean you can write the bind and lift operations, a monad contains the bind and lift operation. The bind and lift operation is a component of the monad. You could probably shoehorn some kind of bind and lift into any conceivable generic type.
Monads in my experience are less likely used for IO than people think. Of course Haskell developers use monads in this way, but OCaml and Scala programmers generally avoid this.
Instead monads are used to unify the foundation of many types: options, lists, trees, etc. Also monads are really nice for futures and parsers.
The point I’m trying to make I guess is that monads are very useful for other things than dealing with impurity, which I think a lot of people just learning them miss.
But in general you can't do that with monads, in particular not if you just have "any" monad at hand but you don't know which one it is. And IO is one example, parsers would be another one.
reply