One of the tricks is the use of monads in Haskell. Monads are really just a small set of functions that operate in a certain way (that "satisfies the monadic laws") on a type.
What that gives you is (among many other things) a way to carry an implicit "world" around with you without needing to refer to it all the time.
One use for such a "world" would be a seed for a random number generator so different parts of your program can use their own, isolated streams of random numbers (nice for determinism, testing, debugging, etc), even if they execute asynchronously because Haskell is a "lazy" language where code doesn't necessarily get evaluated unless it really needs to.
Another use is for the "world" to actually represent the real world -- all of it -- so the I/O interactions happen in the right order. That's very important in a language where the evaluation order is so "loose" as it is in Haskell.
There are other tricks as well. One of them is to have lots of small transformation functions that don't need to understand all of the world, just their little bit of it. You create the big world transformations by composing the smaller transformations. The data structures you use are slightly different: you don't want to modify your data structures, you want to create new ones all the time, i.e. inserting an object into a tree shouldn't modify the tree but return a completely new tree with the new object in it WHILE KEEPING THE OLD TREE AROUND UNCHANGED. This doesn't have to be as expensive as it sounds, in fact, it can be quite cheap. Data structures where you can do this cheaply are called persistent data structures and they work really well with a functional programming style.
(They don't have to implemented purely functionally internally as long as they present a pure interface.)
https://en.wikipedia.org/wiki/Persistent_data_structure
Persistent data structures can also be confluent, which means that they use sharing internally if two versions of a data structure end up being logically the same. Tarjan and Sleator have some really nice papers on that -- well worth reading, even if you don't care one bit about functional programming.
object Foo {
var x: Int = 0
def bar(y: Int) = x + y
}
And trying to figure out what bar will return at any given point. public class MyBigClass() {
private int a;
private int b;
//hundreds of lines of code
private int calculateSomething() {
//do something
a = a+1;
return b*b;
}
//hundreds lines of code
private int calculateSomethingElse() {
int c = calculateSomething();
return a+c;
}
//hundreds of lines of code
public returnSomething() {
return calculateSomethingElse();
}
}
In this example. It's hard because you have to remember constantly which functions are changing your classes before you use them, and if you haven't written the functions, it's very easy to introduce a bug by involuntarily modify the states using the function calculateSomething, just wanting its output and not the modification of a.The guy who is not using encapsulation at all this year will be making similar mistakes (massively long bodies, proliferation of unnecessary entities, etc.) if he switches to functional programming next year. It's not a panacea.
I'm afraid this doesn't demonstrate that any tiny bit of statefulness is "exhausting."
The 'world' is carried on the back of one parameter, right? So if I want to run a function taking two parameters in a particular 'world', which parameter is the Monad? If both of them are, what if they disagree which world is used?
Perhaps I haven't reached the fabled epiphany of monads, but they seem to be just implicit state attached to a single value, and only a single value. I'm not sure what this implicit stuff is better than a system where you are explicitly passing the 'world' that each function needs.
Why is
roll(dieSides: Random<int>) -> int
Clearer than
roll(random: Random, int dieSides) -> int
I get the passing of a world out of a function with a single value
getNumber() -> IO<int>
or using the extra implicit state to represent other possible values, like
Maybe<int>
but, using it to carry around state? That seems to me to be unnecessarily implicit.
What am I missing?
do
(x, g2) <- roll g1
(y, g3) <- roll g2
(z, g4) <- roll g3
it's convenient to have plumbing that hides all those single-use g values.An example of a monad which has really nothing necessarily to do with mutable state are Promises in JavaScript. The container, of course, is a Promise, and the two functions I described are `resolve` and `then`.
In particular the issue I was addressing was the encapsulation of an arbitrary state with a single typed value. It's been remarked elsewhere that 'mixing' monads is a problem: I guess that's always been the thing I've felt haven't made them work replicating as a coding pattern. I tend to prefer explicit over implicit state.
I guess it depends on your definitions, but monadic state is just as explicit as any other.
myMonadicFunction :: String -> State Int String
myMonadicFunction string = do
currentVal <- get
put (currentVal + length string)
return (reverse string)
It's just as clear as what you'd have in an imperative programming language.I'm convinced the main reason monads aren't common in other languages is that most languages just don't have strong enough type systems to support them explicitly. Implicitly they can be found here or there, such as with promises, or just the fact that the semantics of many imperative languages can be viewed as a monad. But of course, probably the biggest reason is that people who are used to mutable state don't see the need. It's one of those things whose value becomes apparent with use.
What's to stop you passing a struct or object with the two monadic functions attached, or ignore the function requirement (for the case of state) and pass the struct with the state and result in it? The benefit being that you can package up more than just one value in that (immutable) structure along with the 'specialness'. I don't see why that is less typesafe.
class Monad m where
return :: a -> m a
(>>=) :: m a -> (a -> m b) -> m b
And it's up to the particular instance to determine how those functions are implemented. For example, instance Monad Maybe where
return :: a -> Maybe a
return x = Just x
(>>=) :: Maybe a -> (a -> Maybe b) -> Maybe b
maybe >>= f = case maybe of
Nothing -> Nothing
Just x -> f x
The definition of the State type, and its corresponding Monad instance, is a little more involved (I suggest this blog post for an explanation [1]): newtype State s a = State { runState :: s -> (a, s) }
instance Monad (State a) where
return x = State $ \s -> (x, s)
m >>= k = State $ \s -> let (a, s') = runState m s
in runState (k a) s'
I'm honestly not quite sure what you mean by the rest of it (for example, "ignoring the function requirement", "specialness" etc). But the bottom line is that you can do any typesafe thing you want to, within the rules of how the language works.[1]: http://brandon.si/code/the-state-monad-a-tutorial-for-the-co...
Sorry, I wasn't talking about Haskell. Following the OP I was talking about the merits of using these concepts or structures in other languages (C++, as per the OP, in particular). Of course, in a language with strong support and syntax for a particular pattern, you should rely on that pattern. But beyond that, it can be useful to use a pattern in other languages. Carmack is talking about writing C++ in a more functional style. I've written OO plain C, for example, when that pattern has made sense, and much of my C++ is heavily influenced by my experience with Scheme.
My point was that I don't get the emphasis on Monads, as a concept, for the range of things it gets emphasised for. In a language which has core syntactic and semantic support, and there are few alternatives, then of course it has to be used widely. But that's a different argument to saying it is an intrinsically good solution for those cases. I might be missing something, but as an architectural pattern in general it doesn't seem appealing. Not enough to reimplement in another language.
That's the context to my comment on typesafety. You said that other languages aren't typesafe enough. I was suggesting that, implementing a monad as a structure or an object would be typesafe, so I couldn't see what was insufficient.
I get the sense that there are people who think Monads are a fundamentally powerful pattern for structuring software. And I don't get that. And I'd like to, because if I'm missing something, it could mean I improve my code architecture.
Monad's qualify as a 'that's an interesting way to get around its self-imposed restrictions' moment when learning Haskell (which I've not used with any seriousness) for me. So I don't get the fuss. I'm not saying that I don't see the logic of those restrictions, nor that I don't see why you'd want to voluntarily code with similar restrictions in other languages. More than, if you're going to write something that has to work around those restrictions in another language, I've no idea why you'd necessarily chose Monads.
Or, put another way, what use are Monads in non Haskell code?