This has one of the usual dangers of trying to do a visualization of a monad, which is that it strongly implies that there is a one-to-one relationship between what you put in and what comes out, which is basically saying that the bind function of the monadic interface implementation will only call the function it receives once. This is not true; it may be called zero times (Nothing, empty list), once (Just something, a list of one element), or many times (once for each element in the list for the list monad, for instance), which is a critical element to understanding the monad interface. So, you know, be aware of that I guess.
If so, let's hope it's more content and less drama... and please, let's concentrate on the content and not some odd analogy that will only take away from said content.
The tech field needs people that are both confident with words, fun and have a deep understanding of the inner details of things.
_why was such a writer, let's hope Adit (correct?) is another.
Regarding form vs. content, I think that the HN community enjoy discussing form as much as content. Most of the people here are interested in design or typography, things that more close to form than to content.
I agree that the tech community could benefit from more well-versed people with insight into our tools and paradigms... however, I don't agree that _why is such a person.
He openly claims to be a poor programmer, and as an artist... well, my contention is that he appeals to a very limited audience. That's not a criticism, simply a suggestion that while he may be valuable in an artistic sense to some people, he's not contributing to the "tech community" in a meaningful way. By which I simply mean that he should not be a yardstick by which we measure anyone.
Let him be himself. Let the rest of us be ourselves. Let that be good enough.
is one of the better gentle into to haskell guides.
I think it started attempting to be sort of similar to why's guide but ended up being a lot more straightforward although there are still some whimsical examples and pictures.
I reluctantly confess that, knowing nothing about Haskell, I was lost by the 4th diagram. Presumably if you know Haskell, the line "data Maybe a = Nothing | Just a" makes sense, but I can't parse it.
I don't know Haskell, but it seems pretty clear: "something called a Maybe named 'a'; is equal to Nothing, or just 'a' itself" (hmm, I'm not sure that's a whole lot clearer :-/ )
The variable 'a' there is a placeholder for a type.
e.g. loosely translating the idea of Maybe to C++ syntax (perhaps more familiar?) might give something resembling:
Maybe<int> x = Just<int>(30);
Maybe<int> y = Nothing<int>();
x and y are values of type Maybe<int>. I.e. we've plugged in the type int for the type variable a.
Given an arbitrary value of type Maybe<int> you have two cases to consider - either it is a "Nothing" which contains no further data, or it is a "Just<int>" which contains an int value.
You can think of the type Maybe<int> as being a "nullable int" type or perhaps a special collection of ints that can only contain 0 or 1 int value.
(beware: my knowledge of both haskell & c++ is not fantastic)
This just defines a new type (called Maybe). The Maybe type can either contain a value or Nothing. This is useful in Haskell because regular values cannot be null. When a function has the potential to fail, it can use one of these Maybe types as its return type to let the caller know of (and handle) the possibility of failure.
As for the syntax itself? It's called a tagged union. Just and Nothing are defined to be constructors for building values of type Maybe a. Just takes one argument (of any type) and Nothing takes no arguments.
Just "hello"
This has type Maybe String.
Nothing
This might also have type Maybe String (or it might not). A function which accepts a Maybe String as one of its arguments will accept Nothing as well.
introduces a new data type. Here a denotes another type which serves as a parameter (an example of a parametrized data type is a list; a list of integers, a list of strings, etc.). In this case we define a data type Maybe a that may contain a value of type a. On the right hand side we have type constructors. They describe different ways that can be used to construct a value of the type we're defening. Here we have two constructors: Nothing which takes no arguments and Just which takes one argument of type a.
Assume that a is the type String. Then Nothing is a value of type Maybe String but it's an empty box that contains no value. On the other hand Just "Hello, World!" is a box with the specified string inside.
Disclaimer: I'm not a Haskell expert. Feel free to correct me.
To read that definition, you need to understand that Haskell has a notion of "type variables", and 'data' is used to introduce new algebraic data types, which you can think of as a sort of tagged union.
The definition states "For all types a, the data type Maybe a consists of 'Nothing' or 'Just a'", where the a in 'Just a' is a value of type a.
Nothing and Just are polymorphic constructors that create the values of any "Maybe a" type.
So if you take a concrete type, say String, you get a new concrete type called "Maybe String" whose values are Nothing, Just "Hello", Just "World", etc. For Maybe Int the values are similarly Nothing (polymorphism!) and Just 1, Just 2, etc.
Extra: 'a' can be any type so Maybe (Maybe Int) is a perfectly valid type, with values like Nothing, Just Nothing, and Just (Just 1). Something like that would be useful when retrieving a "possibly empty" value from a database: Nothing means it's not in the database, Just Nothing means it's there, but it's empty, and Just (Just x) means it's there and it has a value.
Recently, I imlemented the Maybe, Monad, Applicative, and Functor in D. The code should be understandable, if you are comfortable with C++. It uses a mechanism, which is called "traits" in the C++ world. (Note that "traits" means something different in Scala and other languages.)
And if it isn't, and you use -Wall (you really should!) you will get a warning for writing a partial function (which is sometimes warranted, but is a smell).
The function "fromJust" which is equivalent to "getValue" is very rarely useful, and considered a smell: making that one of the primary ways to use your Maybe value is a very bad idea.
Actually, I use Option.get a lot in Scala code. It's very useful for saying, "I expect this Option[A] to be Some[A] every time the code reaches this point due to a proof I cannot (yet) convey to the type-checker, and if it's not, throw an exception for God's sake!"
If you use it a lot it's likely a bad sign. Either that you are not using the type system's power to its full extent, or that the type system is too weak.
In Haskell, use of fromJust is considered a code smell, and is of course sometimes justified. But pretty rarely.
Generally, I use it where I could duplicate an entire data type with additional knowledge of the possible types. So for example, "AST that has been type-specialized and therefore contains no polymorphic type variables" would be a different type from "raw AST" or "type-generalized AST".
I actually did write the code that way the first time around. However, it ended up being a huge duplication of effort for a tiny amount of increased safety from type-checking. Literally, if I'm willing to move the checks of certain type-system-checkable properties to runtime this way, I can write 1/3 the actual lines of code.
The ideal would be to have data structures that are polymorphic over proofs of their properties, allowing the type-system to consider "unchecked AST", "generalized AST" and "specialized AST" as the instantiation of the same basic "AST" data type over three different proofs (or lack of proofs).
Another alternative is sharing the types of the various ASTs but using parameterization to distinguish the cases.
To restore nice, concise types, use a bunch of type synonyms.
While this is slightly cumbersome, I much prefer it over partiality.
Ideally, even this wouldn't be necessary, with structural records and variants that let you work with changing, precise types without having to declare a lot of duplicate types.
Well that's the issue: I want to distinguish otherwise-identical trees in which certain particular node cases are either allowed or disallowed. In Haskell, AFAIK, that's hard. In Scala, it just requires copying your code over and over again, but it's possible and you can even share some of the code.
The other solution I'm thinking of in Scala (that doesn't involve Option[T].get, I mean) is to treat the properties I want "proven" as a set of methods over the data, built as a separate object or trait similar to how Scala represents type-class dictionaries. I can then write one data-structure that quantifies over the type of the proof-witness dictionary it stores as part of itself.
So when, for example, I want a fully-instantiated AST with no remaining "holes" to be filled in by static checking, I could write a function that only works on AST[FullyInstantiated]. Functions that don't care about the proof-witness are quantified over it.
It then takes a bunch of extra typing to construct the proof-witness instances, but when I'm doing that construction I will have the compiler statically checking that all types relied on by the witness are correct.
If I understand you correctly, you want to have a type like
data Foo a = Bar a | Baz a | Qux a
and then also use a type like
data Foo' a = Bar a | Baz a
in certain places.
For one, this sounds like a perfect use case for OCaml's polymorphic variants[1]. I've always liked that particular feature. Honestly, most of the time, it seems like OCaml is the only language doing sub-typing correctly, especially in regards to type inference.
Haskell does not have polymorphic variants, unfortunately. Happily, we can add them in as syntax sugar over multiparameter typeclasses[2]. The very first example in section 1.2 of this paper is particularly relevant here: they're showing how polymorphic variants make it easy to work with different kinds of ASTs. I don't think this is necessarily an immediately practical approach, but it's very interesting from a language design point of view.
Your last example sounds like a prime case for using GADTs[3] (along with data kinds). You could write a type something like:
data Typed = Checked | Unchecked
data AST ∷ Typed → ★ where
Checked' ∷ Int → AST 'Checked
Unchecked' ∷ Int → AST 'Unchecked
Neutral ∷ Int → AST a
Now you can easily write functions expecting unchecked terms, checked terms or both. These functions will have type-checked pattern matching--you will only be able to match on cases for the appropriate type. The compiler can also do exhaustiveness checking based on the type. That is, if you write the following function:
checked ∷ AST 'Checked → Int
checked (Checked' i) = i
you will get a warning that you missed the Neutral pattern, but not the Unchecked' pattern. And if you do try to add the Unchecked' pattern, you will get a type error.
This approach gives you relatively little syntactic overhead at the use site--the appropriate type is chosen based on the constructors you used. You also get nice type-safe pattern matching. I think it's a very good tool for this job. Perhaps you should consider using Haskell more one of these days ;).
Actually, this is a perfect demonstration of why I don't use Haskell. How many compiler extensions did you just invoke to accomplish what I can do trivially and already in Scala? So far, GADTs and Data Kinds. GADTs fall out trivially in Scala's case-classes, and the primary improvement of data-kinds over my witness objects is the ability to do full inference and polymorphism using the Neutral case.
Which I think I can actually do anyway just by declaring the polymorphism, or even using inclusion polymorphism on the witnesses to make Neutral a supertype of Checked and Unchecked (thus letting any function valid for Neutral ASTs operate trivially over both other kinds).
Polymorphic variants are indeed the ideal solution here, the problem being that those require either OCaml or a vastly improved subtyping system. I'm very slowly getting the logic worked out for the vastly improved subtyping system as a side-research project that's been going for years (but really got proper this past November as I started learning more of the logic behind type-theory and thus being able to reason much better about what I'm doing). For now, my own language would probably use structural-extensions and witness objects to do something like this, since it doesn't operate using the Vastly Improved Subtyping System.
I don't see what's wrong with including language features as extensions rather than bundling them into the core language the way Scala does. Sure, you have to explicitly turn them on in your file, but that's trivial. In practice, it's really no different from importing a library.
At the very least, the GADT approach is not hard in Haskell. I don't see how it's any more difficult than the Scala approach. If anything, it's more natural: this is exactly the sort of use case GADTs were designed for.
The main advantage of using GADTs here is that they're simple and explicit. Sure, you could do the same thing with case classes, but it would be more awkward, and you'd really be rebuilding part of the same functionality yourself.
You don't really have to use data kinds in my example, but it makes the code nicer because you explicitly limit the types you can use as flags to 'Checked and 'Unchecked. You could have just made Checked and Unchecked empty types and stayed with normal kinds; the data kind approach is simply more elegant.
The GADT method also ensures that the pattern matching is always safe. It also preserves exhaustiveness checking, which is very nice. I'm not sure if the Scala approach can do this.
Another advantage over using witness objects (assuming I understood your idea correctly) is that you do not introduce anything new at the value level. 'Checked and 'Unchecked are only ever types, so the actual AST ends up being exactly the same as before just with a different type. I think this is a boon to clarity and simplicity. It also means that the use-site of the AST type doesn't change very much, if at all.
Really, the main idea is that this is very easy in Haskell, basically using a single simple abstraction: the GADT. Just being hidden behind a language extension does not magically make the concept any more complicated or difficult to use!
As an aside, if you want polymorphic variants, I don't see any reason not to use OCaml. It's a very nice language, and gets sub-typing right. All without sacrificing type inference.
Still have not encountered a good explanation of monads although there seems to be a new try on Hacker News every couple of weeks. Wonder if/when it will reach mainstream and become part of the common vocabulary of programming. Wonder if a limited syntax would help.
Maybe a better example than the common 'or nothing' would help in this case.
I just coded this today, do you think this is a decent example? Uses monads to do logging around I/O that can fail, in such a way that your logs across multiple threads are guaranteed not to interleave. The code can be improved in many ways, this is a first pass "let's make this compile to prove it works".
I just worked my way through learning-myself-a-haskell yesterday, so this is timely. And I agree with your criticism. The `Maybe` example is simple, and helps with drawings, but it really lacks motivation. I understand all the words, and all the definitions, but it doesn't really explain what problem we've solved.
The payoff of the article is, I think, supposed to be:
getLine >>= readFile >>= putStrLn
Which looks great. But what I'd like to see is a longer explanation of why this is better than the alternatives (like simple function composition).
Simple function composition composes functions. getLine, for instance, isn't a function.
There is a big problem that "Monads" can refer to the generalization and combinators that work with any Monad, or to specific Monad types and the style of composition that they choose to compose values together.
Monadic IO is a solution to combining referential transparency and effectful programs.
Moands in general is a solution to defining the same combinators over and over for each Monad.
I think the biggest problem with monad tutorials is that it seems that everyone finally "gets" monads in a different way so the monad tutorials that get written lack motivation. For example, I finally "got" monads when I was working with a promise library in Javascript...
That said, one thing I noticed is that there are two main "types" of monad tutorials. The first one is the the one taken by the OP and I call it the "category theory" approach. It tends to show what sorts of things you can do with monads and how they work mathematically but they kind of lack an explanation for why you should bother with monads in the first place.
The other approach for monads is the "monads as an useful interface" approach. One paper I would highly recommend in this category is the "awkward squad" paper by Simon Peyton Jones:
Its an introduction to monads that helped me a lot and it also explains quite a bit about the history of monads.
Which lets me come back to your main point:
> why this is better than the alternatives (like simple function composition)
Because simple function composition doesn't work! While nowadays people like to point out that Haskell is a pure functional language, noone would have bothered with making a pure language (back then) if all you gained was all this headache with monads. The reason Haskell was pure in the first place was because its original goal was to be a lazily-evaluated language and that lazyness kind of forces you into pureness. You might think the order of evaluation of function arguments in C being unspecified is bad, but Haskell would be even worse since the order of evaluation for everything is unspecified and some things might not even end up being evaluated at all!
They ended up trying all sorts of approaches for adding effectful computation to Haskell and the most successful one was the monad one (the SPJ paper goes over this a bit). If you pay attention, the monad interface forces computations to be single threaded (you can't "branch out" monadic effects) and doesn't let you generally start or end an effectful computation wherever you want, meaning that whoever defined the monad you are using can control how effects are started or terminated via what functions they expose in the public interface (Maybe exposes the constructors directly so you can pattern match on them but IO makes it so `main` the only place you can start an IO computation)
Finally, this gets us back to notational problems. While monads make things really neat in the type level and as a platform for effects, it can end up really noisy in practice:
getNumber >>= (\a -> getNumber >>= (\b -> f a b)))
So for monads we have "do" notation that lets you write things in a neater way:
a <- getNumber
b <- getNumber
f a b
This is really helpful and 99% of the time, do notation is precisely why people bother making things into monads instead of defining their own versions of (>>=), like they do in Javascript with promises. However, writing all those intermediate variables can be annoying and doesn't feel "composeable". The Functor and Applicative type classes provide methods to do that:
f <$> getNumber <*> getNumber
Monads are always particular cases of these type classes and you can show this mathematically but its a bit complicated in practice because people added monads to Haskell before they discovered these otehr type classes were useful...
To wrap things up I would like to point out that most of the time monads and functors are an "advanced" technique that lets you take programs that you could already write before but write them in a neater way, with do notation, fmap, <$>, etc. (While its true that you can write generic programs that abstract over the monad interfac...
I think one of the problems of most Haskell tutorials for beginners is assuming well known facts or knowledge in Haskell is also known to the readers, which is a pretty wrong assumption. Haskell has its own set of vocabulary that first timers would have difficult to grasp.
A simple example in the blog, it talks about Just 2 or Just a but don't explain what Just is. Also 'a' as a parameter for type is not explained. Leaving the readers to wonder whether a is argument to a function or it is a value since Just 2 and Just a are mentioned in the same section.
Also a lot of tutorials are afraid to draw parallel concepts from other languages that would be familiar with the readers, instead preferring to invent abstract definition to build things up. Most people learn by relating and inferring from familiar concepts.
E.g. Functor is just like template in C++ for constructing type. As a template it has placeholders to let the programmer to fill in to build a concrete type. These placeholders are the parameters to the functor. The programmer supplies a type as the parameter to the functor to construct a concrete type. E.g. "List a" is a functor which is called List and its parameter is a. "List int" would build a concrete type for values of list of int.
Maybe and Just are not a good introduction functor for beginners. Few other languages have a similar concept and people just roll their eyes when they learn what it is since it's not as useful in general.
List is a better introduction to functor. Most programmers have work with List and know what it is and its problems relating to its element type. Seeing "List int" people immediately know it's defining a list of int, and they know right the way what functor is and what it is for. Doing a couple more examples of List float, List string, and people will see why functor is very useful. May be adding Tree or some other functor operator like Eq or Show to how to have generic behaviors applying to any types.
This has nothing to do with functors (yet). MyBox has a so-called kind of
MyBox :: * -> *
meaning that MyBox takes a type (namely a) and returns a type. For instance, MyBox Int has the kind:
MyBox Int :: *
Now, a Functor is any data type of kind
* -> *
for which we can implement the function fmap
fmap :: (a -> b) -> f a -> f b
Such that:
fmap id = id
fmap (p . q) = (fmap p) . (fmap q)
hold. We can implement this function for MyBox and show that the laws hold (left as an exercise to the reader):
instance Functor MyBox where
fmap f (MyBox v) = MyBox (f v)
In other words, fmap applies a function within a context. And the fact that a particular context (such as MyBox) is a Functor states that the functor laws hold.
Yup, this post assumes some knowledge of Haskell. Writing a post about monads that assumes no knowledge of Haskell is like writing a post about polymorphism that assumes no knowledge of OO :)
The whole presentation of monads as contexts (containers that store values) is very deceptive in my opinion. Especially when following Functor and Applicative. This creates an illusion that Monad is about modifying values just like Functor/Applicative, while, in fact, it's mostly about modifying the context.
I had a great success understanding Monad as a sequential program. The simplest way to write such a program would be just to chain elementary statements with the >> operator, however, to do any kind of control this way you'd need to rely on implicit state (think of programming in Assembler where you do branches based on implicit flag registers set by the previous instructions and the branch target itself is implicitly a new program).
So Monad is just the minimal improvement over this that allows control structures without need of implicit conventions. It achieves this with three simple additions to the basic sequential model:
1. add explicit results to the statements
2. allow a switch after each statement based on its results
3. add pure elementary statements (that don't do anything other than returning the value they have been created with) so we could transform results without modifying the context.
You can still do the simple chaining of statements in the Monad but whenever you need conditional/switch - you can do it explicitly with the >>= operator, whose argument is essentially a switch table, that assigns a new sequence of statements for each value.
You still need to figure out how do the List monad takes multiple branches in its switches simultaneously but it might be easier for a programmer to understand than container-value analogy.
Yikes. That might as well be titled "Why functional languages will never catch on." It took something dead simple (a function that adds 3), and made it insanely complicated. Why would anyone choose to deal with that?
They chose a deliberately over-simplified example in order to demonstrate the core concepts; I don't begrudge them that (nobody needs a program that prints out "Hello World!").
The audience level of this piece is confusing -- it looks like a friendly, cartoony introduction for a general audience, but it actually assumes you already know Haskell.
Basically, you and I are not the intended audience.
Right. I'm hoping to make monads easier to understand, but you need some knowledge of functional programming to build on. If I had tried catering to the complete beginner, this post would be much longer and not as focused :)
> They chose a deliberately over-simplified example in order to demonstrate the core concepts;
i wish people who are writing guides on these topics pick a non-simplified situation where using this method makes for an easier program (vs using the "traditional method"). The comparison and contrasting of a functional/monadic approach vs a procedural approach in a complex scenario might actually shed more light - at least, for some one who is familiar with the procedural approach.
I would love to see this! One of the fundamental issues I have with functional languages is every example I see is basically "See this thing that's relatively simple and uncomplicated in Ruby/PHP/C/whatever? Here's how to do that in Haskell!" And the Haskell version ends up being extremely unintuitive and complicated. Bottom line is I still don't know for which situations functional languages are a "good" tool.
I think the problem is that the Haskell version is actually very simple, to the person writing it. Its a classical problem of teaching, it is extremly easy to forget how something looks to new people.
That approach is used at length in Real World Haskell. For example, some code for key-value lookup is shown that deals with a series of Maybe foo values, first with a cascade of guards (think a cascade of if-else's), and later with monad syntax. There are other examples.
I enjoyed RWH much more after having learned a lot of the basics. It starts off simple, to be sure, but then quickly jumps into the deep end. For a book that's about real-world stuff it gets into a lot of unnecessary detail, too, that should have been deferred until later "advanced" chapters. I appreciated the hands-on approach to real examples, though.
"Learn You A Haskell For Great Good" [1], while much less complete, is the best intro I have found so far. It's also free.
I know a bit about Haskell and have played with it. I guess it just seems like functional languages are for those who want to solve super complicated mental exercises when doing tasks that would otherwise be easy using a traditional language.
There are very few things in the world of CS/programming where I can't follow along and grok what's going on, especially when laid out in a tutorial form like this. The fact that I was totally lost when reading this is not a good sign when you're trying to get people to adopt your school of thought.
This is a pretty common idea, but it's because pure functional programming is, at first, all about getting you to significantly change your programming POV. It's a shift for anyone---in fact, the better you are at understanding C/Ruby/Python/Perl/Java/ObjC/Lua the harder a shift it is.
At the end of the day, you adopt a new way of thinking that is incredibly clarifying for all programming. One simple thing is that it allows you to "see" where state, re-entry, evaluation, IO, and failure are happening in a program---things that you are blind to when you're used to languages where the answer is "always".
This article isn't even meant to explain why you ought to learn Haskell. It's more like a signpost on your path to mastery, should you begin walking it.
>There are very few things in the world of CS/programming where I can't follow along and grok what's going on, especially when laid out in a tutorial form like this.
That's probably because most of the tutorials you've followed have adopted an imperative style of programming. It is easy for you to follow along because it really isn't all that different from what you are accustomed to. Functional programming really is a different beast entirely. It's often said (though I remain skeptical) that it's actually easier to learn functional programming as a non-programmer than to un-learn your imperative programming tendencies.
>The fact that I was totally lost when reading this is not a good sign when you're trying to get people to adopt your school of thought.
I don't think that's a fair assessment. You could say the same thing about starting from functional programming and switching to imperative; some people actually do this! They find the idea of mutating a variable to be completely counter-intuitive!
Except it isn't. Unless you learn imperative programming before you learn basic algebra, the first definition of a variable you encounter is that of an immutable one.
Actually an algebraic variable is named that way because you can change it every time you do the exercise.
So you don't change it during one exercise, but you can and certainly will change it when you change the exercise to another one with the same principle.
No, that's an example of recursion. With recursion you aren't mutating the variable, you're shadowing its definition. This is a subtle, but important, distinction.
Recursion is how you implement a sum without mutation (an imperative loop is how you'd implement it with mutation). You've merely given me the function written in a declarative notation.
Yeah, you're talking about the mathematical definition of a bound variable. Functional programming languages use this whenever you define a function:
foo x = x * 2
Every time you call foo, x is bound to a new value (but only within the scope of foo). This is not the same as mutating some memory location associated with the name x.
x does not mutate at all. x is simply a placeholder for the values in the series of additions that your expression expands to, that is, 1² + 2² + 3² + ... + 10². Or stepwise, to 1² + sum_{x=2}^{10}x² etc. all three expressions are equivalent, and no mutation of x occurs anywhere
The series of additions can also be infinite, in which case it would be quite difficult to expand fully, but that doesn't prevent you from doing mathematics with it.
That behaves just like a function: the values of the variables change each time you call the function but not within. When you do a new exercise, it's really a bunch of new variables with the same name--variables cannot affect each other between exercises!
While going through a Math undergrad program in the mid-90s, I noticed that some number of my fellow students had a distaste for programming.
As someone who'd done reasonably well with both and had thought of them as heavily overlapping areas, I thought this was interesting, so I tried to poke at this a bit.
One of the concepts I got back from a talented classmate was basically a complaint about mutable variables -- that in an algebraic description of relationships for a given system what we call "variables" are less variable and more "unknowns" which represent fixed if unidentified quantities. Others similarly noted there was something absurd about writing out equations like "x = x + 2".
I can see why they think that, but the important thing is that "x = x + 2" is not an equation. It's just an arguably poorly chosen operator (Pascal gets this right and uses ":=" for assignment).
The trick is to stop reading that line (in your head) as "x is equal to x plus two", but instead "x is assigned the value of x plus two".
> It's just an arguably poorly chosen operator (Pascal gets this right and uses ":=" for assignment).
That's one potential takeaway, but when I turned over the complaint in my mind, this was arguably another way of complaining about mutability.
Also, given the timing, it's pretty likely that one of the languages they'd learned was Pascal: that was the language of the first few CS classes at my school in the 90s, it was also the language of any high school curriculum that led to the AP exam, and Borland's Pascal products were still pretty popular PC dev environments.
They could've also been unlucky and received their first exposure by choosing to do a numerical analysis class in Fortran or C, of course. :/
People who exclusively use Excel spreadsheets to program is a good example. Most people who use spreadsheets never use functions in a cell whose effect is to change the function or value in another cell.
To the other people saying that pure functional programming is the reason that it's difficult to explain Haskell to other programmers and blaming them for doing too much imperative style coding, it's the wrong assumption. Pure functional programming has nothing to do with the difficulty in explaining Haskell. Pure functional programming is in Lisp/Schema/C#/Clojure/and other languages and people have no trouble learning it.
Haskell is difficult to explain because it has many terms, vocabulary, and syntax that are not familiar to people. It's ML-style syntax doesn't help.
Most tutorials targeting Haskell beginners implicitly assume the readers already know the Haskell syntax, terms, definition, etc, and go right ahead to explain the advance concepts like functor, applicative, or monad. It's like teaching them how to fly before they know how to walk, in the Haskell land. No wonder Haskell beginners are frustrated with any of the monad tutorial.
The thing is. Stop. Stop telling beginners what monad is. Just show them the basics in Haskell and get something useful going. Get them to write and use Haskell programs. When the need arise, they will look into what monad is. Programmers have used STL plenty without knowing the internals of C++ templates. Same thing can be done with monad.
In other communities, new tools and abstractions are generally "sold" by explaining how they are useful for solving specific problems. Not so in the Haskell community though.
The Haskell community has a longstanding tradition of using condescending language ("fmap is from the street, fmap is hip to contexts."), and deliberate childrens-book style metaphors and illustrations in tutorials.
The theory seem to be that if monads haven't been adopted by mainstream developers yet, it is just because the presentations haven't been dumbed-down and kiddie-friendly enough yet. (If only they from the onset had called "monads" for "warm fuzzy things" instead, surely developers would not be so scared of them!)
The function is not the focus. The article presents three abstractions that allow you to apply regular functions on things to which they are not directly applicable.
Maybe is usually used as an example since it's the simplest non-trivial monad (and therefore also an applicative functor).
The point of eg. fmap etc. is that you can have regular Int -> Bool functions, and apply them to data with more structure than simple values. a Maybe String is not a String, but you can still transform one to uppercase by doing fmap (map toUpper) mstring. The fun part is that that exact same thing works on a list of strings, an IO String (fmap (map toUpper) getLine), a Tree of Strings, etc... It's fully generic over anything that can implement Functor; the exact behaviour is defined by the particular instance.
Applicative is just an extension to the idea, where the function applied itself can have this extra structure, and when combined with an infix fmap you get to do
(+) <$> Just 2 <*> Nothing <*> Just 5 -- Nothing
(++) <$> getLine <*> getLine -- IO action that reads two lines and concats them
To see why this works has to do with currying. Just stare at the types of fmap and <* > long enough and you should see it eventually.
a Monad is yet more powerful because it allows a previous computation to affect what follows in an arbitrary manner (Applicative doesn't), allowing things like state, asynchronous execution, continuations, and countless other things. It's such an useful abstraction that Haskell provides the do syntax for using it.
Edit: In languages like Java where all types are nullable, if you uppercase a String, but that String is null, your program breaks, even though the type system claims that you have a String when in reality all types are Maybes, and you are forced to always check for nulls manually or just rely on users to not give you nulls, even though it would be trivial for the compiler to check something like that statically if the language were expressive enough to allow it.
So if I understand correctly, the Maybe monad is analogous to SQL's handling of NULL. In SQL, I can invoke the UPPER function on a string, and if that string is a string I get the uppercase version, if it's a NULL I get a NULL in return. I get that, but every time I read about Monads (to the GP's point) it just seems like a very formal academic theory and I keep thinking "so what?" I have not done much with Haskell, maybe it's one of those concepts that seems more natural in practice than it does to try to read about it?
The big difference is that it's statically computed.
Rust also has a Maybe type as well. You use it like this:
use core::io::{Reader,ReaderUtil};
use core::io::println;
fn main() {
let in = io::stdin().read_line();
println("INPUT:");
println(int::str(int::from_str(in)));
}
This code will produce an error, because `int::from_str` can fail. Imagine trying to convert a number from the string "foobar".
$ make
rustc option.rs
option.rs.rs:125:23: 125:41 error: mismatched types: expected `int` but found
`core::option::Option<int>` (expected int but found enum core::option::Option)
option.rs:125 io::println(int::str(int::from_str(in)));
^~~~~~~~~~~~~~~~~~
error: aborting due to previous error
make: *** [build] Error 101
What Rust is saying here is that we don't have the right types: `int::str` takes an int, but `int::from_str` returns an `Option<int>`.
We can make the compiler satisfied by pattern matching over the return value:
use core::io::{Reader,ReaderUtil};
use core::io::println;
fn main() {
let in = io::stdin().read_line();
match int::from_str(in) {
Some(number_string) => println(int::to_str(number_string)),
None => println("Hey, put in a number.")
}
}
Now, we explicitly handle both cases (Some(x) being a success, and None being the failure), and the compiler is happy.
This is where the advantages of Maybe come into play. In dynamic languages, you don't get the advantage of always making sure you handle the degenerate case.
Now, what the Maybe _monad_ does is allow you to chain multiple things that work with Maybe together. I don't have a Rust example of this handy. Like "Do this, do that, do the other thing, and then let's see if that failed anywhere along the way."
The reason why people talk so much about monads is that they are ubiquitous, and Haskell in particular can make good use of them because if type classes and other high level abstraction facilities, plus do notation.
Asynchronous execution is one particular case that I like as an example: You can implement promises (which represent async computations) in Haskell and such a Promise type will also be able to implement the monad type class. Because they are monads, you can use do notation:
-- Let's assume we want to fetch two large datasets asynchronously over the network and filter them
asyncSanitize :: Promise [String] -> Promise [String] -> Promise [String]
asyncSanitize p1 p2 = do
x <- p1
y <- p2
return (sanitize (x ++ y)) -- no callbacks!
which looks like the same code you would use for filtering two Maybe values using do notation. In fact, it is the same code, and if you leave out the type signature, then "asyncSanitize" will work for any monadic value: you could easily test your function using eg. Identity [String] (pure values), IO String (synchronous fetching), or decide to switch to some Database monad instead that fetches the values on demand from the Database. The code will not need to change at all.
Sort of like NULL, especially in that both NULL and Nothing mean "the absence of a value" and cannot be combined or compared with anything else. But it goes way beyond that.
Think of Maybe as a container. It either "contains" a value, or it doesn't. The container itself is not usable except for passing around; it's the contained value that is important. Even if we know that the container is supposed to contain a number, we can't use the container to perform computations; every time we need the number, we need to open the container, peek inside, and decide what to do whether it's contains something or nothing.
In Haskell terms, this "peeking inside" is typically done with pattern matching, which is core to the type system. For example, this is a function that checks if a Maybe value has anything, and returns true or false:
hasValue x =
case x of
Just _ -> True
Nothing -> False
The line "Just _" is a kind of wildcard expression, meaning: "Check if x matches the pattern Just [something]".
At the opposite end, we can have functions that produce Maybes (naive and simplified example, not working code):
Notice how "Just" looks like a function call. That's because it is. It's a function that "constructs" a value of, in this case, type "Maybe String". The "Nothing" returned is also a value of type "Maybe String". Thanks to Haskell's type inference, this is known at compile time.
In languages such as Java or Ruby we can build classes that are containers that act in a similar way. But those languages are limited in that "peek inside the container" logic needs to happen at runtime. In Haskell, the container's possible values are enforced entirely by the type system at compile time.
To really appreciate this, you really have to be familiar with statically typed languages such as C++ or Java. In such languages, you have "nil" or "null" or "NULL" values to represent "nothing". Any time you have a pointer, you have to explicitly check if the pointer is valid; if you try to use it, and it happens to be null, the program will blow up at runtime, but not at compile time.
In Haskell, there is no generic null value. The value "Nothing" on its own belongs to a specific type, such as "Maybe String", and won't work with another such as "Maybe Integer". Thus when we want to use "fetchPage", we are forced into a situation where must make a choice based on whether there is a value or not (again, naive example):
crawlPageRecursively url =
do contents <- fetchPage url
case contents of
Just s -> map crawlPageRecursively (map (\body -> getLinks body) s)
Nothing -> putStrLn ("Failed to fetch " ++ url)
Since Haskell doesn't have null pointers, and can never end up in a situation where you try to use a null where you expected a value, because its type system makes it impossible.
This was my attempt at "explain it like I'm five" Haskell as carefully as I could. The real world is somewhat more complex, but not awfully so. As you say, too often haskellers will get into "formal academic theory", even when, in my opinion, it's not necessary. Haskell becomes more elegant and beautiful if you study and know group theory, but most people don't, and it really just obscures the language for ordinary, mortal programmers who are trying to learn it.
As for monads, it's true that Maybe is a monad, but think of it as a convenience, in the sense that this lets it play well with the rest of the monad infrastructure. You can use, and even implement, Maybe without involving the monad aspect.
My best suggestion regarding monads is to just start using the language; as DanWaterworth points out, monads are pretty much inexplicable for beginners until one really needs to use them. Haskel...
"functional languages" is a rather wide concept, but I would accept "Why this particular aspect of functional languages will never catch on". Many other aspects have already been assimilated by pratically everyone.
Have you seen how much complexity Java has just for hello world!? It's truly absurd. And, as somebody who's tried teaching Java to complete beginners, I can attest that it isn't intuitive at all.
There's no way this Java thing will catch on. Why would anyone choose to deal with it?
Haskell and Java are not the only language in the world. The poster might prefer an OO-language without the HelloWorld-overhead as Java. Like, say, Python where hello world is literally
print "Hello World"
And in the case of Java, it is well known that Java is not intended or used for writing one-lines, so it is not really a relevant criticism. And even if it were, it doesn't refute any criticism of Haskell that you are able to point out unrelated shortcomings in an unrelated language.
My point was that it hasn't stopped Java from becoming popular--more popular than Python, even! After all, people do choose to deal with all the wanton complexity of Java.
Also, the example of hello world doesn't show much about complexity. After all, in Haskell it's just:
> (+) <$> (Just 5)
Just (+5)
> Just (+5) <$> (Just 4)
ERROR ??? WHAT DOES THIS EVEN MEAN WHY IS THE FUNCTION
WRAPPED IN A JUST
A functor takes a function, and applies it to the inside of a context, but all of a sudden now a functor can't take a function and apply it to the inside of a context.
No, that will never make sense. Sorry. Haskell is hard.
edit: oh, it's in a just value, I didn't notice that the first time I read it, for... some reason.
>A functor takes a function, and applies it to the inside of a context, but all of a sudden now a functor can't take a function and apply it to the inside of a context.
Where you're getting mixed up is in the distinction between Functors and Applicative Functors. You can think of an Applicative Functor as one which allows you to apply its contents to other things. One function which must be defined for all Applicative Functors is:
(<*>) :: f (a -> b) -> f a -> f b
Looking at the type signature, this is a function of two arguments. The first argument is an Applicative Functor containing a function. The second argument is the same functor containing a value to which that function may be applied. What does this function do? It simply applies the function and returns the result, wrapped in the same Functor. Thus:
Just (+5) <*> Just 4
>>> Just 9
This is very much analogous to typical function application:
(+5) 4
>>> 9
Which is why the function (<*>) is usually pronounced "ap" or "applied over".
I think that many monad tutorials, including this one, fall into the same trap. They explain monads without giving any motivation, or when they do, it seems like monads only exist to get around problems caused by being pure.
When you program in normal languages, you have the ability to perform effects all of the time. You can always read/write to/from files, you can always raise exceptions or otherwise change the control flow. There's nothing you can't do.
Haskell is different. In Haskell, by default, you don't have any effects at your disposal at all. You can create values from other values, that's it. When you want to program with effects, you use a monad. The type of monad that you use decides the effects that you are allowed. For example, the maybe monad instance gives you the "fail without an error effect". The either monad gives you the "fail with an error effect". The list monad instance gives you the non-determinism effect. etc. You might expect me to say that IO gives you the "interact with the outside world effect", but that's not really true, forget what you know about IO for the moment.
The continuation monad instance is a bit special. In a sense, it is the most general of the monad types, because every monad instance can be seen as a specialization of it. The continuation monad allows every kind of effect. At this point, you might be thinking, "but how can this be, it doesn't allow IO".
To reconcile these facts. I present the coroutine monad. It has an effect, yield, that gives a value to the caller and waits for the caller to respond with a value so that it can continue. Here are some types:
runCoroutine :: Coroutine i o x -> Either (o, i -> Coroutine i o x) x
yield :: o -> Coroutine i o i
So running a coroutine either produces a value of o and function to resume the coroutine or it completes with a value of x. IO is very similar to the coroutine monad. When a Haskell program gets to a function that talks to the outside world it yields control to the runtime environment, the runtime environment does the actual IO and passes the result back so that the program can continue.
This is why Haskell is pure despite having effects. Haskell programs don't do IO, they yield control to the runtime environment whenever IO needs to be done.
So why are monads useful? They allow you to restrict the effects that are possible in different parts of your programs. This aids in reasoning about your programs, because you don't have to think about whole classes of behaviour, but they're less useful in other languages where you already have effects everywhere.
I don't know how else to put it, but at least half of what you have written above is completely wrong.
> but they're less useful in other languages where you already have effects everywhere.
Scala has side effects everywhere, yet the Monad is extensively used in Scala programs. Monads have nothing to do with "effects".
Monads are not used to decide the kind of "effects" you are allowed. By calling every Monad a kind of "effect", you are just begging the question. Monads are merely objects that follow 3 monad laws, no more no less.
> This is why Haskell is pure despite having effects. Haskell programs don't do IO, they yield control to the runtime environment whenever IO needs to be done.
How is this different from a C program? Haskell is pure in the presence of IO, because the type system tracks IO calls and forces any function that even indirectly refers to a quantity obtained via IO to modify its type signature to reflect the IO action. This can be done with/without Monads. Haskell IO is a Monad, because it pretty much analogous to the State Monad and Haskell has special Monad syntax that lets you pass the World State implicitly and write imperative code in an imperative style.
Haskell IO is pure because an IO action is basically just an "instruction". For example, one way to make a pure thing out of a side-effecting procedure is to wrap it in a lambda; it won't do anything until you actually call it, so you can use it as a value. The reason why IO is "separated" by the type system is the same as why Strings are "separated" from Ints: They are different.
The monad operations happen to be a convenient interface for combining IO values to form a composite value that is then called "main", and when the program is actually run, the value of main is executed to produce all the effects.
"Pure" typically means referentially transparent. Wrapping a side-effecting procedure in a lambda does not make it referentially transparent, therefore it isn't pure.
It is referentially transparent. The lambda (or thunk) itself can be used freely as a value. For example
newtype PureIO a = PureIO (() -> a)
print :: String -> PureIO ()
print s = PureIO $ \_ -> unsafePrint s -- impure primitive
If the user only has access to the "print" variable, then they can only use it as a pure value. two applications of print "foo" would result in two thunks, either of which, when actually invoked, would cause the side-effect of printing "foo".
Invoking the pure IO values is something only the runtime can do, so referential transparency is not violated.
The important thing here is not the wrapping with a lambda. That's not what made it pure.
The important thing here is the abstract type wrapper. You're exposing pure primitives that are implemented in terms of (hidden) impure ones.
That's why code that uses unsafePerformIO can be pure and referentially transparent, as long as the exposed primitives are pure and implementation details are hidden.
To demonstrate this, imagine that you did expose the PureIO data constructor -- then values of type PureIO would not be referentially transparent anymore.
> at least half of what you have written above is completely wrong.
That's quite an assertion.
> Scala has side effects everywhere, yet the Monad is extensively used in Scala programs. Monads have nothing to do with "effects".
I didn't say monads weren't used outside of Haskell, I say that they are less useful. I haven't programmed in Scala before, I assume monads are used for their notation. They aren't required.
> Monads are not used to decide the kind of "effects" you are allowed. By calling every Monad a kind of "effect", you are just begging the question. Monads are merely objects that follow 3 monad laws, no more no less.
I don't know if you're familiar with group theory, but in group theory, every finite group is a specialization of the group of permutations. Groups are defined abstractly by the operations that they allow in the same way that monads are, but this definition for finite groups is as general as saying that every group is a subgroup of the group of permutations.
It's the same for monads, they are defined abstractly, but the abstract definition is equivalent to saying monads are all of the things that you can create by a specific kind of specialization of the continuation monad.
Continuations are an effect. Specializations of continuations are also effects. Monads define effects. What's really interesting is thinking about monads in terms of the Curry-Howard Isomorphism, on one end of the scale, the identity monad, you have constructive logic and at the other end, continuations, you have classical logic, what's in the middle? Does the list monad instance have an interesting associated logic?
Which question am I begging?
> How is this different from a C program?
It's just a different way of looking at programs. The distinction is never drawn for C programs.
> Haskell is pure in the presence of IO, because the type system tracks IO calls and forces any function that even indirectly refers to a quantity obtained via IO to modify its type signature to reflect the IO action.
This is quite a shallow way of looking at it.
> This can be done with/without Monads.
Sure, you could use continuations or uniqueness types.
> Haskell IO is a Monad, because it pretty much analogous to the State Monad
AFAIK, that's not defined in the Haskell standard, but that is the way that GHC does it.
> Haskell has special Monad syntax that lets you pass the World State implicitly and write imperative code in an imperative style.
No, it's not syntax that's letting you pass the world state implicitly. In GHC:
IO x = State RealWorld x = (RealWorld -> (RealWorld, x))
You can't get at this function and RealWorld doesn't have any constructors, so you can't call an IO operation or do other weird things. Anyway, it doesn't really matter how IO is actually defined. What matters is it's interface.
Reading your response, I think I need to read more on the topic. I haven't read Moggi's paper and I need to look up on the continuation Monad. You are probably correct and I am probably wrong. However, I will let my original post remain as a matter of historical record :-)
The RealWorld hack in GHC is not "passing the world state". It's just a hack/implementation detail to get evaluation order correctly. The RealWorld -> (RealWorld, x) view of IO is not a valid one.
Quite correct. As I'm aware, when you call "main" in Haskell, you're actually building up a really gigantic curried function of type `IO x` (where x is your return type), and then the runtime system runs everything in the curried function all at once. The `RealWorld` symbol is more-or-less an existential type: we can't do anything with it, but it exists to make the type system acknowledge the monadic nature of IO so that we can use monadic combinators to order our curried I/O actions in preparation for their eventual run on real hardware.
He's not begging the question at all. In formal PL terms, a monad and a computational effect (which includes continuations, nondeterminism, and I/O, and loads more interesting stuff) are very strongly connected concepts. If I remember correctly, they may even be equivalent.
Monadic combinators are really just kind of type-sugar. Think: any ordering of computations can be expressed as a tree. However, the only ordering trees a pure-functional language must obey are call-trees (the parameters of a function must be computed before the results of a function, even after lazy-evaluation is taken into account). Monadic programming is a way to transform arbitrary ordering trees of possibly-dynamic depth and width into call trees while preserving the original ordering.
The really neat thing about monads is that they statically guarantee they'll preserve the original ordering, despite its being of dynamic width and depth.
There are plenty of effects IO (the ambient monad of most languages) doesn't support:
You can't do ListT fork effects.
You can't do ContT effects (coroutines are not as powerful as ContT).
You can't have Parsec effects (you need to write parser combinators as in Haskell).
The Monad generalization is useful for working with nullables, lists, and the effectful view of Monads is only one way to see them.
When using languages "without monads" I do miss Control.Applicative and Control.Monad very much.
That's a good point. I was mistaken by saying that, but if Javascript is anything to go by, when people have IO but not continuations, they'll just program in CPS. As I said, once you have continuations, you can create everything else.
> the effectful view of Monads is only one way to see them
Absolutely, but what's interesting is that it is as general as any other view of monads.
What I was trying to say is that monadic combinators are super-useful in Haskell as they are in Javascript.
They're hard to implement in Javascript though, because of return type polymorphism or explicit dictionary passing being required. So people don't implement them -- not because Monads aren't useful there, but because Monads are hard to implement there.
I think you're falling into the same trap you identify. You've squeezed your actual justification into those two lines at the end of your post, and they're far too abstract to persuade me to actually learn Haskell. How about talking through a debugging session that was made much simpler because of this isolation of effects, or something similarly direct?
That's a fair criticism. I think the reason that happened was because its just so much more natural to talk abstractly about Haskell :)
Here's a recent experience I can share: I've been working on a Haskell project for three days now, I've been type-checking as go, but I haven't been running the program I'm writing. This is mainly because I started "in the middle" and so I couldn't just run it, I'd have had to have written supporting code.
I've finally got to the stage where I _can_ run it. I've just tried it now and thanks to the style in which I write Haskell, where I clamp down quite severely on unnecessary effects/complexity before they become issues, it works perfectly. Creating specialized monads is just one aspect of the style of writing. This wasn't a trivial project either, I've essentially written a cross between a database and distributed source control.
"Bill O’Reilly being totally ignorant about the Maybe functor" and the corresponding pic was (for me) LOL funny ;-)
My intro to functional programming has been via Scala; not quite Haskell, but makes use of some of the same functional idioms, so a good place to get your feet wet in FP if you're tied to the JVM (yes, yes, there's also Clojure, prefer Scala's type system and easily grok-able syntax).
If you learn Haskell without getting into these stuff directly it's soo much easier. It will obvious and not cryptic like if you tackle this without any basic understanding of Haskell.
This guide is for understanding the theory behind monads, the use should not be something difficult to get if you have used the IO monad before.
One thing that bothers me, but I found no explanation why - is there a way to turn 'Just a' to 'a'? >>= seems like it does that but I only see it working with function that returns a Monad.
Just think about it. Let's call such a function fromJust:
fromJust :: Maybe a -> a
fromJust (Just x) = x
fromJust Nothing = ??? -- What can we put here?
Haskell doesn't have a null value that we could use here. There is such a function defined in the standard library, but it is partial. It uses error in the Nothing case.
edit:
In many cases there is a clear value that should be used in the Nothing case, suppose you have a Maybe Int which is the number of times a specific event happened in the last second. Clearly 0 is a good default and you can write a function (Maybe Int -> Int). There's a function I tend to use for such cases: (maybe :: b -> (a -> b) -> Maybe a -> b). In this case:
Maybe a is a value that may or may not contain an a within it. So how would you convert that to an a, which definitely has an a in it?
What you can do, is pass a callback to a function that handles the case where you do have an "a" in the Maybe. And that's basically what fmap, <$>, and >>= do.
That last picture was problematic in that it showcased three different conventions for application:
fx
f <*> x
x >>= f
The first of these (see also the Monads section) was the author's fault (should have used f <$> x), but the last conjures memories of PHP. Something to expand on? I would have also preferred more reminders that the box is the Functor/Applicative/Monad, not the function or the infix operator.
127 comments
[ 2.9 ms ] story [ 205 ms ] threadWould you have a specific suggestion to improve his presentation?
And I've given Maybe's Monad instance definition as well as explained it in pictures. If there's something missing please let me know specifically.
The tech field needs people that are both confident with words, fun and have a deep understanding of the inner details of things.
_why was such a writer, let's hope Adit (correct?) is another.
Regarding form vs. content, I think that the HN community enjoy discussing form as much as content. Most of the people here are interested in design or typography, things that more close to form than to content.
He openly claims to be a poor programmer, and as an artist... well, my contention is that he appeals to a very limited audience. That's not a criticism, simply a suggestion that while he may be valuable in an artistic sense to some people, he's not contributing to the "tech community" in a meaningful way. By which I simply mean that he should not be a yardstick by which we measure anyone.
Let him be himself. Let the rest of us be ourselves. Let that be good enough.
_why even said this himself:
> caller asks, “should i use hpricot or nokogiri?” if you're NOT me: use nokogiri. and if you're me: well cut it out, stop being me.
is one of the better gentle into to haskell guides.
I think it started attempting to be sort of similar to why's guide but ended up being a lot more straightforward although there are still some whimsical examples and pictures.
e.g. loosely translating the idea of Maybe to C++ syntax (perhaps more familiar?) might give something resembling:
x and y are values of type Maybe<int>. I.e. we've plugged in the type int for the type variable a.Given an arbitrary value of type Maybe<int> you have two cases to consider - either it is a "Nothing" which contains no further data, or it is a "Just<int>" which contains an int value.
You can think of the type Maybe<int> as being a "nullable int" type or perhaps a special collection of ints that can only contain 0 or 1 int value.
(beware: my knowledge of both haskell & c++ is not fantastic)
As for the syntax itself? It's called a tagged union. Just and Nothing are defined to be constructors for building values of type Maybe a. Just takes one argument (of any type) and Nothing takes no arguments.
This has type Maybe String. This might also have type Maybe String (or it might not). A function which accepts a Maybe String as one of its arguments will accept Nothing as well.Assume that a is the type String. Then Nothing is a value of type Maybe String but it's an empty box that contains no value. On the other hand Just "Hello, World!" is a box with the specified string inside.
Disclaimer: I'm not a Haskell expert. Feel free to correct me.
The definition states "For all types a, the data type Maybe a consists of 'Nothing' or 'Just a'", where the a in 'Just a' is a value of type a.
Nothing and Just are polymorphic constructors that create the values of any "Maybe a" type.
So if you take a concrete type, say String, you get a new concrete type called "Maybe String" whose values are Nothing, Just "Hello", Just "World", etc. For Maybe Int the values are similarly Nothing (polymorphism!) and Just 1, Just 2, etc.
Extra: 'a' can be any type so Maybe (Maybe Int) is a perfectly valid type, with values like Nothing, Just Nothing, and Just (Just 1). Something like that would be useful when retrieving a "possibly empty" value from a database: Nothing means it's not in the database, Just Nothing means it's there, but it's empty, and Just (Just x) means it's there and it has a value.
https://bitbucket.org/qznc/d-monad/src/1ec4ca36e46df1d0fce41...
The function "fromJust" which is equivalent to "getValue" is very rarely useful, and considered a smell: making that one of the primary ways to use your Maybe value is a very bad idea.
Is much less safe than: And has no useful advantage...In Haskell, use of fromJust is considered a code smell, and is of course sometimes justified. But pretty rarely.
I actually did write the code that way the first time around. However, it ended up being a huge duplication of effort for a tiny amount of increased safety from type-checking. Literally, if I'm willing to move the checks of certain type-system-checkable properties to runtime this way, I can write 1/3 the actual lines of code.
The ideal would be to have data structures that are polymorphic over proofs of their properties, allowing the type-system to consider "unchecked AST", "generalized AST" and "specialized AST" as the instantiation of the same basic "AST" data type over three different proofs (or lack of proofs).
To restore nice, concise types, use a bunch of type synonyms.
While this is slightly cumbersome, I much prefer it over partiality.
Ideally, even this wouldn't be necessary, with structural records and variants that let you work with changing, precise types without having to declare a lot of duplicate types.
The other solution I'm thinking of in Scala (that doesn't involve Option[T].get, I mean) is to treat the properties I want "proven" as a set of methods over the data, built as a separate object or trait similar to how Scala represents type-class dictionaries. I can then write one data-structure that quantifies over the type of the proof-witness dictionary it stores as part of itself.
So when, for example, I want a fully-instantiated AST with no remaining "holes" to be filled in by static checking, I could write a function that only works on AST[FullyInstantiated]. Functions that don't care about the proof-witness are quantified over it.
It then takes a bunch of extra typing to construct the proof-witness instances, but when I'm doing that construction I will have the compiler statically checking that all types relied on by the witness are correct.
For one, this sounds like a perfect use case for OCaml's polymorphic variants[1]. I've always liked that particular feature. Honestly, most of the time, it seems like OCaml is the only language doing sub-typing correctly, especially in regards to type inference.
[1]: http://caml.inria.fr/pub/docs/manual-ocaml-4.00/manual006.ht...
Haskell does not have polymorphic variants, unfortunately. Happily, we can add them in as syntax sugar over multiparameter typeclasses[2]. The very first example in section 1.2 of this paper is particularly relevant here: they're showing how polymorphic variants make it easy to work with different kinds of ASTs. I don't think this is necessarily an immediately practical approach, but it's very interesting from a language design point of view.
[2]: http://guppy.eng.kagawa-u.ac.jp/~kagawa/PVH/PolymorphicVaria...
Your last example sounds like a prime case for using GADTs[3] (along with data kinds). You could write a type something like:
Now you can easily write functions expecting unchecked terms, checked terms or both. These functions will have type-checked pattern matching--you will only be able to match on cases for the appropriate type. The compiler can also do exhaustiveness checking based on the type. That is, if you write the following function: you will get a warning that you missed the Neutral pattern, but not the Unchecked' pattern. And if you do try to add the Unchecked' pattern, you will get a type error.[3]: http://www.haskell.org/ghc/docs/6.6/html/users_guide/gadt.ht...
This approach gives you relatively little syntactic overhead at the use site--the appropriate type is chosen based on the constructors you used. You also get nice type-safe pattern matching. I think it's a very good tool for this job. Perhaps you should consider using Haskell more one of these days ;).
Which I think I can actually do anyway just by declaring the polymorphism, or even using inclusion polymorphism on the witnesses to make Neutral a supertype of Checked and Unchecked (thus letting any function valid for Neutral ASTs operate trivially over both other kinds).
Polymorphic variants are indeed the ideal solution here, the problem being that those require either OCaml or a vastly improved subtyping system. I'm very slowly getting the logic worked out for the vastly improved subtyping system as a side-research project that's been going for years (but really got proper this past November as I started learning more of the logic behind type-theory and thus being able to reason much better about what I'm doing). For now, my own language would probably use structural-extensions and witness objects to do something like this, since it doesn't operate using the Vastly Improved Subtyping System.
At the very least, the GADT approach is not hard in Haskell. I don't see how it's any more difficult than the Scala approach. If anything, it's more natural: this is exactly the sort of use case GADTs were designed for.
The main advantage of using GADTs here is that they're simple and explicit. Sure, you could do the same thing with case classes, but it would be more awkward, and you'd really be rebuilding part of the same functionality yourself.
You don't really have to use data kinds in my example, but it makes the code nicer because you explicitly limit the types you can use as flags to 'Checked and 'Unchecked. You could have just made Checked and Unchecked empty types and stayed with normal kinds; the data kind approach is simply more elegant.
The GADT method also ensures that the pattern matching is always safe. It also preserves exhaustiveness checking, which is very nice. I'm not sure if the Scala approach can do this.
Another advantage over using witness objects (assuming I understood your idea correctly) is that you do not introduce anything new at the value level. 'Checked and 'Unchecked are only ever types, so the actual AST ends up being exactly the same as before just with a different type. I think this is a boon to clarity and simplicity. It also means that the use-site of the AST type doesn't change very much, if at all.
Really, the main idea is that this is very easy in Haskell, basically using a single simple abstraction: the GADT. Just being hidden behind a language extension does not magically make the concept any more complicated or difficult to use!
As an aside, if you want polymorphic variants, I don't see any reason not to use OCaml. It's a very nice language, and gets sub-typing right. All without sacrificing type inference.
Scala already has GADTs! http://lambdalog.seanseefried.com/posts/2011-11-22-gadts-in-... So yes, I use GADTs already.
Maybe a better example than the common 'or nothing' would help in this case.
https://gist.github.com/dustingetz/5606201
The payoff of the article is, I think, supposed to be:
getLine >>= readFile >>= putStrLn
Which looks great. But what I'd like to see is a longer explanation of why this is better than the alternatives (like simple function composition).
My goal was just to provide a gentle introduction.
There is a big problem that "Monads" can refer to the generalization and combinators that work with any Monad, or to specific Monad types and the style of composition that they choose to compose values together.
Monadic IO is a solution to combining referential transparency and effectful programs.
Moands in general is a solution to defining the same combinators over and over for each Monad.
That said, one thing I noticed is that there are two main "types" of monad tutorials. The first one is the the one taken by the OP and I call it the "category theory" approach. It tends to show what sorts of things you can do with monads and how they work mathematically but they kind of lack an explanation for why you should bother with monads in the first place.
The other approach for monads is the "monads as an useful interface" approach. One paper I would highly recommend in this category is the "awkward squad" paper by Simon Peyton Jones:
https://research.microsoft.com/en-us/um/people/simonpj/Paper...
Its an introduction to monads that helped me a lot and it also explains quite a bit about the history of monads.
Which lets me come back to your main point:
> why this is better than the alternatives (like simple function composition)
Because simple function composition doesn't work! While nowadays people like to point out that Haskell is a pure functional language, noone would have bothered with making a pure language (back then) if all you gained was all this headache with monads. The reason Haskell was pure in the first place was because its original goal was to be a lazily-evaluated language and that lazyness kind of forces you into pureness. You might think the order of evaluation of function arguments in C being unspecified is bad, but Haskell would be even worse since the order of evaluation for everything is unspecified and some things might not even end up being evaluated at all!
They ended up trying all sorts of approaches for adding effectful computation to Haskell and the most successful one was the monad one (the SPJ paper goes over this a bit). If you pay attention, the monad interface forces computations to be single threaded (you can't "branch out" monadic effects) and doesn't let you generally start or end an effectful computation wherever you want, meaning that whoever defined the monad you are using can control how effects are started or terminated via what functions they expose in the public interface (Maybe exposes the constructors directly so you can pattern match on them but IO makes it so `main` the only place you can start an IO computation)
Finally, this gets us back to notational problems. While monads make things really neat in the type level and as a platform for effects, it can end up really noisy in practice:
So for monads we have "do" notation that lets you write things in a neater way: This is really helpful and 99% of the time, do notation is precisely why people bother making things into monads instead of defining their own versions of (>>=), like they do in Javascript with promises. However, writing all those intermediate variables can be annoying and doesn't feel "composeable". The Functor and Applicative type classes provide methods to do that: Monads are always particular cases of these type classes and you can show this mathematically but its a bit complicated in practice because people added monads to Haskell before they discovered these otehr type classes were useful...To wrap things up I would like to point out that most of the time monads and functors are an "advanced" technique that lets you take programs that you could already write before but write them in a neater way, with do notation, fmap, <$>, etc. (While its true that you can write generic programs that abstract over the monad interfac...
A simple example in the blog, it talks about Just 2 or Just a but don't explain what Just is. Also 'a' as a parameter for type is not explained. Leaving the readers to wonder whether a is argument to a function or it is a value since Just 2 and Just a are mentioned in the same section.
Also a lot of tutorials are afraid to draw parallel concepts from other languages that would be familiar with the readers, instead preferring to invent abstract definition to build things up. Most people learn by relating and inferring from familiar concepts.
E.g. Functor is just like template in C++ for constructing type. As a template it has placeholders to let the programmer to fill in to build a concrete type. These placeholders are the parameters to the functor. The programmer supplies a type as the parameter to the functor to construct a concrete type. E.g. "List a" is a functor which is called List and its parameter is a. "List int" would build a concrete type for values of list of int.
Maybe and Just are not a good introduction functor for beginners. Few other languages have a similar concept and people just roll their eyes when they learn what it is since it's not as useful in general.
List is a better introduction to functor. Most programmers have work with List and know what it is and its problems relating to its element type. Seeing "List int" people immediately know it's defining a list of int, and they know right the way what functor is and what it is for. Doing a couple more examples of List float, List string, and people will see why functor is very useful. May be adding Tree or some other functor operator like Eq or Show to how to have generic behaviors applying to any types.
No, a type variable is! For instance, if we have a data type:
a is the type variable, and is pretty much the same thing as a C++ class or struct template that stores a (const) value: This has nothing to do with functors (yet). MyBox has a so-called kind of meaning that MyBox takes a type (namely a) and returns a type. For instance, MyBox Int has the kind: Now, a Functor is any data type of kind for which we can implement the function fmap Such that: hold. We can implement this function for MyBox and show that the laws hold (left as an exercise to the reader): In other words, fmap applies a function within a context. And the fact that a particular context (such as MyBox) is a Functor states that the functor laws hold.I had a great success understanding Monad as a sequential program. The simplest way to write such a program would be just to chain elementary statements with the >> operator, however, to do any kind of control this way you'd need to rely on implicit state (think of programming in Assembler where you do branches based on implicit flag registers set by the previous instructions and the branch target itself is implicitly a new program).
So Monad is just the minimal improvement over this that allows control structures without need of implicit conventions. It achieves this with three simple additions to the basic sequential model: 1. add explicit results to the statements 2. allow a switch after each statement based on its results 3. add pure elementary statements (that don't do anything other than returning the value they have been created with) so we could transform results without modifying the context.
You can still do the simple chaining of statements in the Monad but whenever you need conditional/switch - you can do it explicitly with the >>= operator, whose argument is essentially a switch table, that assigns a new sequence of statements for each value.
You still need to figure out how do the List monad takes multiple branches in its switches simultaneously but it might be easier for a programmer to understand than container-value analogy.
http://web.cecs.pdx.edu/~antoy/Courses/TPFLP/lectures/MONADS...
The audience level of this piece is confusing -- it looks like a friendly, cartoony introduction for a general audience, but it actually assumes you already know Haskell.
Basically, you and I are not the intended audience.
i wish people who are writing guides on these topics pick a non-simplified situation where using this method makes for an easier program (vs using the "traditional method"). The comparison and contrasting of a functional/monadic approach vs a procedural approach in a complex scenario might actually shed more light - at least, for some one who is familiar with the procedural approach.
http://book.realworldhaskell.org/read/programming-with-monad...
"Learn You A Haskell For Great Good" [1], while much less complete, is the best intro I have found so far. It's also free.
[1] http://learnyouahaskell.com/
There are very few things in the world of CS/programming where I can't follow along and grok what's going on, especially when laid out in a tutorial form like this. The fact that I was totally lost when reading this is not a good sign when you're trying to get people to adopt your school of thought.
At the end of the day, you adopt a new way of thinking that is incredibly clarifying for all programming. One simple thing is that it allows you to "see" where state, re-entry, evaluation, IO, and failure are happening in a program---things that you are blind to when you're used to languages where the answer is "always".
This article isn't even meant to explain why you ought to learn Haskell. It's more like a signpost on your path to mastery, should you begin walking it.
That's probably because most of the tutorials you've followed have adopted an imperative style of programming. It is easy for you to follow along because it really isn't all that different from what you are accustomed to. Functional programming really is a different beast entirely. It's often said (though I remain skeptical) that it's actually easier to learn functional programming as a non-programmer than to un-learn your imperative programming tendencies.
>The fact that I was totally lost when reading this is not a good sign when you're trying to get people to adopt your school of thought.
I don't think that's a fair assessment. You could say the same thing about starting from functional programming and switching to imperative; some people actually do this! They find the idea of mutating a variable to be completely counter-intuitive!
The notion of mutation is implicit in the definition of "variable."
So you don't change it during one exercise, but you can and certainly will change it when you change the exercise to another one with the same principle.
For example, if you integrate x^2 dx from x=0 to 10, x changes continuously.
It's clear that x is not fixed to any particular value.
The series of additions can also be infinite, in which case it would be quite difficult to expand fully, but that doesn't prevent you from doing mathematics with it.
That's not analogous at all to mutation.
As someone who'd done reasonably well with both and had thought of them as heavily overlapping areas, I thought this was interesting, so I tried to poke at this a bit.
One of the concepts I got back from a talented classmate was basically a complaint about mutable variables -- that in an algebraic description of relationships for a given system what we call "variables" are less variable and more "unknowns" which represent fixed if unidentified quantities. Others similarly noted there was something absurd about writing out equations like "x = x + 2".
The trick is to stop reading that line (in your head) as "x is equal to x plus two", but instead "x is assigned the value of x plus two".
That's one potential takeaway, but when I turned over the complaint in my mind, this was arguably another way of complaining about mutability.
Also, given the timing, it's pretty likely that one of the languages they'd learned was Pascal: that was the language of the first few CS classes at my school in the 90s, it was also the language of any high school curriculum that led to the AP exam, and Borland's Pascal products were still pretty popular PC dev environments.
They could've also been unlucky and received their first exposure by choosing to do a numerical analysis class in Fortran or C, of course. :/
Source: all my classmates.
Haskell is difficult to explain because it has many terms, vocabulary, and syntax that are not familiar to people. It's ML-style syntax doesn't help.
Most tutorials targeting Haskell beginners implicitly assume the readers already know the Haskell syntax, terms, definition, etc, and go right ahead to explain the advance concepts like functor, applicative, or monad. It's like teaching them how to fly before they know how to walk, in the Haskell land. No wonder Haskell beginners are frustrated with any of the monad tutorial.
The thing is. Stop. Stop telling beginners what monad is. Just show them the basics in Haskell and get something useful going. Get them to write and use Haskell programs. When the need arise, they will look into what monad is. Programmers have used STL plenty without knowing the internals of C++ templates. Same thing can be done with monad.
> When the need arise, they will look into what monad is.
I couldn't have said it any better. This is the exact scenario that this post is aimed at.
The Haskell community has a longstanding tradition of using condescending language ("fmap is from the street, fmap is hip to contexts."), and deliberate childrens-book style metaphors and illustrations in tutorials.
The theory seem to be that if monads haven't been adopted by mainstream developers yet, it is just because the presentations haven't been dumbed-down and kiddie-friendly enough yet. (If only they from the onset had called "monads" for "warm fuzzy things" instead, surely developers would not be so scared of them!)
The strategy have not yet borne fruit, though.
Maybe is usually used as an example since it's the simplest non-trivial monad (and therefore also an applicative functor).
The point of eg. fmap etc. is that you can have regular Int -> Bool functions, and apply them to data with more structure than simple values. a Maybe String is not a String, but you can still transform one to uppercase by doing fmap (map toUpper) mstring. The fun part is that that exact same thing works on a list of strings, an IO String (fmap (map toUpper) getLine), a Tree of Strings, etc... It's fully generic over anything that can implement Functor; the exact behaviour is defined by the particular instance.
Applicative is just an extension to the idea, where the function applied itself can have this extra structure, and when combined with an infix fmap you get to do
To see why this works has to do with currying. Just stare at the types of fmap and <* > long enough and you should see it eventually.a Monad is yet more powerful because it allows a previous computation to affect what follows in an arbitrary manner (Applicative doesn't), allowing things like state, asynchronous execution, continuations, and countless other things. It's such an useful abstraction that Haskell provides the do syntax for using it.
Edit: In languages like Java where all types are nullable, if you uppercase a String, but that String is null, your program breaks, even though the type system claims that you have a String when in reality all types are Maybes, and you are forced to always check for nulls manually or just rely on users to not give you nulls, even though it would be trivial for the compiler to check something like that statically if the language were expressive enough to allow it.
Rust also has a Maybe type as well. You use it like this:
This code will produce an error, because `int::from_str` can fail. Imagine trying to convert a number from the string "foobar". What Rust is saying here is that we don't have the right types: `int::str` takes an int, but `int::from_str` returns an `Option<int>`.We can make the compiler satisfied by pattern matching over the return value:
Now, we explicitly handle both cases (Some(x) being a success, and None being the failure), and the compiler is happy.This is where the advantages of Maybe come into play. In dynamic languages, you don't get the advantage of always making sure you handle the degenerate case.
Now, what the Maybe _monad_ does is allow you to chain multiple things that work with Maybe together. I don't have a Rust example of this handy. Like "Do this, do that, do the other thing, and then let's see if that failed anywhere along the way."
Asynchronous execution is one particular case that I like as an example: You can implement promises (which represent async computations) in Haskell and such a Promise type will also be able to implement the monad type class. Because they are monads, you can use do notation:
which looks like the same code you would use for filtering two Maybe values using do notation. In fact, it is the same code, and if you leave out the type signature, then "asyncSanitize" will work for any monadic value: you could easily test your function using eg. Identity [String] (pure values), IO String (synchronous fetching), or decide to switch to some Database monad instead that fetches the values on demand from the Database. The code will not need to change at all.Think of Maybe as a container. It either "contains" a value, or it doesn't. The container itself is not usable except for passing around; it's the contained value that is important. Even if we know that the container is supposed to contain a number, we can't use the container to perform computations; every time we need the number, we need to open the container, peek inside, and decide what to do whether it's contains something or nothing.
In Haskell terms, this "peeking inside" is typically done with pattern matching, which is core to the type system. For example, this is a function that checks if a Maybe value has anything, and returns true or false:
The line "Just _" is a kind of wildcard expression, meaning: "Check if x matches the pattern Just [something]".At the opposite end, we can have functions that produce Maybes (naive and simplified example, not working code):
Notice how "Just" looks like a function call. That's because it is. It's a function that "constructs" a value of, in this case, type "Maybe String". The "Nothing" returned is also a value of type "Maybe String". Thanks to Haskell's type inference, this is known at compile time.In languages such as Java or Ruby we can build classes that are containers that act in a similar way. But those languages are limited in that "peek inside the container" logic needs to happen at runtime. In Haskell, the container's possible values are enforced entirely by the type system at compile time.
To really appreciate this, you really have to be familiar with statically typed languages such as C++ or Java. In such languages, you have "nil" or "null" or "NULL" values to represent "nothing". Any time you have a pointer, you have to explicitly check if the pointer is valid; if you try to use it, and it happens to be null, the program will blow up at runtime, but not at compile time.
In Haskell, there is no generic null value. The value "Nothing" on its own belongs to a specific type, such as "Maybe String", and won't work with another such as "Maybe Integer". Thus when we want to use "fetchPage", we are forced into a situation where must make a choice based on whether there is a value or not (again, naive example):
Since Haskell doesn't have null pointers, and can never end up in a situation where you try to use a null where you expected a value, because its type system makes it impossible.This was my attempt at "explain it like I'm five" Haskell as carefully as I could. The real world is somewhat more complex, but not awfully so. As you say, too often haskellers will get into "formal academic theory", even when, in my opinion, it's not necessary. Haskell becomes more elegant and beautiful if you study and know group theory, but most people don't, and it really just obscures the language for ordinary, mortal programmers who are trying to learn it.
As for monads, it's true that Maybe is a monad, but think of it as a convenience, in the sense that this lets it play well with the rest of the monad infrastructure. You can use, and even implement, Maybe without involving the monad aspect.
My best suggestion regarding monads is to just start using the language; as DanWaterworth points out, monads are pretty much inexplicable for beginners until one really needs to use them. Haskel...
There's no way this Java thing will catch on. Why would anyone choose to deal with it?
Also, the example of hello world doesn't show much about complexity. After all, in Haskell it's just:
No, that will never make sense. Sorry. Haskell is hard.
edit: oh, it's in a just value, I didn't notice that the first time I read it, for... some reason.
(+5) <$> (Just 4)
Where you're getting mixed up is in the distinction between Functors and Applicative Functors. You can think of an Applicative Functor as one which allows you to apply its contents to other things. One function which must be defined for all Applicative Functors is:
Looking at the type signature, this is a function of two arguments. The first argument is an Applicative Functor containing a function. The second argument is the same functor containing a value to which that function may be applied. What does this function do? It simply applies the function and returns the result, wrapped in the same Functor. Thus: This is very much analogous to typical function application: Which is why the function (<*>) is usually pronounced "ap" or "applied over".When you program in normal languages, you have the ability to perform effects all of the time. You can always read/write to/from files, you can always raise exceptions or otherwise change the control flow. There's nothing you can't do.
Haskell is different. In Haskell, by default, you don't have any effects at your disposal at all. You can create values from other values, that's it. When you want to program with effects, you use a monad. The type of monad that you use decides the effects that you are allowed. For example, the maybe monad instance gives you the "fail without an error effect". The either monad gives you the "fail with an error effect". The list monad instance gives you the non-determinism effect. etc. You might expect me to say that IO gives you the "interact with the outside world effect", but that's not really true, forget what you know about IO for the moment.
The continuation monad instance is a bit special. In a sense, it is the most general of the monad types, because every monad instance can be seen as a specialization of it. The continuation monad allows every kind of effect. At this point, you might be thinking, "but how can this be, it doesn't allow IO".
To reconcile these facts. I present the coroutine monad. It has an effect, yield, that gives a value to the caller and waits for the caller to respond with a value so that it can continue. Here are some types:
So running a coroutine either produces a value of o and function to resume the coroutine or it completes with a value of x. IO is very similar to the coroutine monad. When a Haskell program gets to a function that talks to the outside world it yields control to the runtime environment, the runtime environment does the actual IO and passes the result back so that the program can continue.This is why Haskell is pure despite having effects. Haskell programs don't do IO, they yield control to the runtime environment whenever IO needs to be done.
So why are monads useful? They allow you to restrict the effects that are possible in different parts of your programs. This aids in reasoning about your programs, because you don't have to think about whole classes of behaviour, but they're less useful in other languages where you already have effects everywhere.
> but they're less useful in other languages where you already have effects everywhere.
Scala has side effects everywhere, yet the Monad is extensively used in Scala programs. Monads have nothing to do with "effects".
Monads are not used to decide the kind of "effects" you are allowed. By calling every Monad a kind of "effect", you are just begging the question. Monads are merely objects that follow 3 monad laws, no more no less.
> This is why Haskell is pure despite having effects. Haskell programs don't do IO, they yield control to the runtime environment whenever IO needs to be done.
How is this different from a C program? Haskell is pure in the presence of IO, because the type system tracks IO calls and forces any function that even indirectly refers to a quantity obtained via IO to modify its type signature to reflect the IO action. This can be done with/without Monads. Haskell IO is a Monad, because it pretty much analogous to the State Monad and Haskell has special Monad syntax that lets you pass the World State implicitly and write imperative code in an imperative style.
The monad operations happen to be a convenient interface for combining IO values to form a composite value that is then called "main", and when the program is actually run, the value of main is executed to produce all the effects.
Invoking the pure IO values is something only the runtime can do, so referential transparency is not violated.
The important thing here is the abstract type wrapper. You're exposing pure primitives that are implemented in terms of (hidden) impure ones.
That's why code that uses unsafePerformIO can be pure and referentially transparent, as long as the exposed primitives are pure and implementation details are hidden.
To demonstrate this, imagine that you did expose the PureIO data constructor -- then values of type PureIO would not be referentially transparent anymore.
That's quite an assertion.
> Scala has side effects everywhere, yet the Monad is extensively used in Scala programs. Monads have nothing to do with "effects".
I didn't say monads weren't used outside of Haskell, I say that they are less useful. I haven't programmed in Scala before, I assume monads are used for their notation. They aren't required.
> Monads are not used to decide the kind of "effects" you are allowed. By calling every Monad a kind of "effect", you are just begging the question. Monads are merely objects that follow 3 monad laws, no more no less.
I don't know if you're familiar with group theory, but in group theory, every finite group is a specialization of the group of permutations. Groups are defined abstractly by the operations that they allow in the same way that monads are, but this definition for finite groups is as general as saying that every group is a subgroup of the group of permutations.
It's the same for monads, they are defined abstractly, but the abstract definition is equivalent to saying monads are all of the things that you can create by a specific kind of specialization of the continuation monad.
Continuations are an effect. Specializations of continuations are also effects. Monads define effects. What's really interesting is thinking about monads in terms of the Curry-Howard Isomorphism, on one end of the scale, the identity monad, you have constructive logic and at the other end, continuations, you have classical logic, what's in the middle? Does the list monad instance have an interesting associated logic?
Which question am I begging?
> How is this different from a C program?
It's just a different way of looking at programs. The distinction is never drawn for C programs.
> Haskell is pure in the presence of IO, because the type system tracks IO calls and forces any function that even indirectly refers to a quantity obtained via IO to modify its type signature to reflect the IO action.
This is quite a shallow way of looking at it.
> This can be done with/without Monads.
Sure, you could use continuations or uniqueness types.
> Haskell IO is a Monad, because it pretty much analogous to the State Monad
AFAIK, that's not defined in the Haskell standard, but that is the way that GHC does it.
> Haskell has special Monad syntax that lets you pass the World State implicitly and write imperative code in an imperative style.
No, it's not syntax that's letting you pass the world state implicitly. In GHC:
You can't get at this function and RealWorld doesn't have any constructors, so you can't call an IO operation or do other weird things. Anyway, it doesn't really matter how IO is actually defined. What matters is it's interface.Monadic combinators are really just kind of type-sugar. Think: any ordering of computations can be expressed as a tree. However, the only ordering trees a pure-functional language must obey are call-trees (the parameters of a function must be computed before the results of a function, even after lazy-evaluation is taken into account). Monadic programming is a way to transform arbitrary ordering trees of possibly-dynamic depth and width into call trees while preserving the original ordering.
The really neat thing about monads is that they statically guarantee they'll preserve the original ordering, despite its being of dynamic width and depth.
There are plenty of effects IO (the ambient monad of most languages) doesn't support:
You can't do ListT fork effects. You can't do ContT effects (coroutines are not as powerful as ContT). You can't have Parsec effects (you need to write parser combinators as in Haskell).
The Monad generalization is useful for working with nullables, lists, and the effectful view of Monads is only one way to see them.
When using languages "without monads" I do miss Control.Applicative and Control.Monad very much.
> the effectful view of Monads is only one way to see them
Absolutely, but what's interesting is that it is as general as any other view of monads.
They're hard to implement in Javascript though, because of return type polymorphism or explicit dictionary passing being required. So people don't implement them -- not because Monads aren't useful there, but because Monads are hard to implement there.
Here's a recent experience I can share: I've been working on a Haskell project for three days now, I've been type-checking as go, but I haven't been running the program I'm writing. This is mainly because I started "in the middle" and so I couldn't just run it, I'd have had to have written supporting code.
I've finally got to the stage where I _can_ run it. I've just tried it now and thanks to the style in which I write Haskell, where I clamp down quite severely on unnecessary effects/complexity before they become issues, it works perfectly. Creating specialized monads is just one aspect of the style of writing. This wasn't a trivial project either, I've essentially written a cross between a database and distributed source control.
My intro to functional programming has been via Scala; not quite Haskell, but makes use of some of the same functional idioms, so a good place to get your feet wet in FP if you're tied to the JVM (yes, yes, there's also Clojure, prefer Scala's type system and easily grok-able syntax).
This guide is for understanding the theory behind monads, the use should not be something difficult to get if you have used the IO monad before.
EDIT: I may have skimmed a bit.
edit: In many cases there is a clear value that should be used in the Nothing case, suppose you have a Maybe Int which is the number of times a specific event happened in the last second. Clearly 0 is a good default and you can write a function (Maybe Int -> Int). There's a function I tend to use for such cases: (maybe :: b -> (a -> b) -> Maybe a -> b). In this case:
What you can do, is pass a callback to a function that handles the case where you do have an "a" in the Maybe. And that's basically what fmap, <$>, and >>= do.
[1] http://adit.io/imgs/functors/recap.png
> the last conjures memories of PHP
As a non-PHP programmer I'm not sure what you mean.