Ah, I finally figured out the Reader monad. My mistake was thinking that it was usable in the normal way that monads are, via independent bind calls, but it's not. In order to get any use out of it, you *must* do nested binds, or more reasonably, you must use do notation.
Reader and Writer are always presented together, I assume so you can then segue straight into the State monad that kinda-sorta does both of them together (but not really), but the two are actually *completely* different things which work in 100% unrelated ways. I hate Haskell tutorials. >_<
Related: HOLY SHIT I HATE HASKELL TUTORIALS. In a "basics" tutorial for the Reader monad, someone actually
thought that this was a reasonable, instructive line to write:
Code: Select all
greetAndSeeYou = liftM seeYou nameReturn >>= putStrLn
This example uses two "threading" functions (functions that modify the way data flows through other functions) - liftM and >>=.
Unforgivable Sin 1: It uses one in function form, and the other in operator form.
Unforgivable Sin 2: Despite the two being basically identical (one maps across a functor, the other maps across a monad), they take their arguments in opposite order.
Unforgivable Sin 3: As a result of this, the actual data flow is "call nameReturn, map seeYou over the result, map putStrLn over the result". MIDDLE-ENDIAN DATAFLOW, IN A FUCKING BASIC TUTORIAL. WHAT.
Unforgivable Sin 4: No parens used, so a beginner has to have already intuited the way Haskell function calls work, and the arg count of each function involved, to understand that it should be organized as "(liftM seeYou nameReturn) >>= putStrLn".
I mean, Haskell syntax is fucked to begin with. It got its core concepts frozen back before we realized how to organize them properly, so everything's ass-backwards and inconsistent. A slightly more reasonable way to write this would be:
Code: Select all
greetAndSeeYou = putStrLn =<< (seeYou `liftM` nameReturn)
This at least uses the threading functions (a) in the same form (both operators) and (b) in the same argument order (LHS function, RHS value that function will be mapped over). This is the more logical order, too, as it makes the threading function into an analog of normal function application - you're calling the function with an arg, but the arg is actually in a wrapper and this automagically gets handled for you. It's how <$> and <*> work already for Applicatives! WHY DO THE THREE CLOSELY-RELATED CORE CONCEPTS IN HASKELL HAVE THREE COMPLETELY DIFFERENT USE CONVENTIONS?!?
Functor: function syntax, takes function arg and then functor arg
Applicative: operator syntax, takes function arg and then applicative arg
Monad: operator syntax, takes monad arg and then function arg
I love FP. I hate Haskell. Argh.