=====================[ Readings ]===================== Please read Chapters 7, 14 of "Real World Haskell" *after* you work through these notes. ==============[ The great usefulness of Maybe ]============== Reporting that a function or a pipeline of functions has _not_ been able to come up with a successful result is crucial. Consider the case of the IP geolocation service whose API failed to define the type of such a result, and used "special" coordinates instead: http://fusion.net/story/287592/internet-mapping-glitch-kansas-farm/ Misled by its API, its clients (including Internet giants and federal agencies!) assumed those "special" coordinates were real, with embarrassing results. Haskell allows the programmer to "join" one or more special values to the main result type of a function or expression (or even a pipeline of functions), and to announce it in the type signature. Moreover, it auto-generates the code that handles these values automatically, passing them harmlessly through the pipeline, no matter at which stage the failure has occurred. Compare this with how you'd define these functions in C or Ruby. In C, you will likely use a tagged union of structs, with one common tag field in each struct of the union; the tag would be an enum of constants for each case of different contents. Whenever handling a value, you'd look up this ptr->tag and interpret the rest of the struct differently based on it, in a switch statement, case by case, recasting the ptr to the proper type. In Ruby, you would explicitly check what .class returns, and act accordingly. But in each case you will need to write all the checks explicitly, in your mainline code. See maybe-class-notes-nov10-2016.txt for the in-class example, and maybe-and-data-list-log.txt for another similar log. [UPDATED:] ==============[ >>= as a core element of Haskell style ]============== The ability to write code unburdened by control flow exceptions like throw/catch, nested ifs, and similar is very important to Haskell. For this and other reasons (discussed below), >>= is at the heart of how Haskell does the most basic programming action: chaining two operations together, to be executed in sequence. In C or Java we simply chain operations with ";" and think no more of it. In LISP and Scheme, where every expression has a value, we use PROGN or its implicit equivalents like LET to make a list of expressions, and say that its value is that of the last expression in the list. But none of this lets us deal with exceptional conditions or side-effects: these must be handled separately. Not so in Haskell. In Haskell, something like "let f = 1 ; 2" simply won't parse. You can use the seq function "let f = 1 `seq` 2" , but its purpose is to force evaluation of the first argument, removing laziness. Seq is great for keeping large chains of thunks from accumulating when doing something simple like summing a list, but stripping laziness is not the same thing as pipelining operations. Besides, seq doesn't help if we want pipelines that can pass an exceptional value: Prelude> Nothing `seq` error "Bang" *** Exception: Bang Prelude> Nothing >> error "Bang" Nothing Haskell has notation for writing operations in sequence, as if writing in an imperative language: the "do notation". This notation is syntax sugar for >>= and >> : do action1 action2 action3 translates ("desugars") to action1 >> do action2 action3 and then, of course, to "action1 >> action2 >> action3" For actions that pass a value down the pipeline, the do notation uses "<-" to catch the values, and then do x1 <- action1 x2 <- action2 action3 x1 x2 desugars to action1 >>= \ x1 -> action2 >>= \ x2 -> action3 x1 x2 Note that the above doesn't need explicit parentheses, because of >>= being declared as "infixlr 1", i.e., associating to the left, just as you expect with a pipeline: (action1 >>= (\ x1 -> action2) ) >>= (\ x2 -> (action3 x1 x2)) See more about desugaring in http://www.haskellforall.com/2014/10/how-to-desugar-haskell-code.html