9 ms·
A Gentle Intro to Monads … Maybe?
- deleted 13y ago[deleted]
- flebron 13y agoIt seems you think all Monads are Maybe, or List?
- jerf 13y agoThis criticism makes no sense; I don't know what you think monadic computations are, but it isn't what you think. I'd be more specific, but I honestly don't know what you're thinking. The fact that the posted explanation is wrong doesn't help, I'm sure.
- dllthomas 13y agoUh, no. This precludes the possibility of saying "I know this cannot fail (modulo seriously crazy situations like hardware failure or OOM)", which lets you significantly reduce the number of spurious exceptional conditions you need to handle, which reduces code noise if you actually try and treat every impossible exceptional case as if it were possible and increases safety if you don't.
- jerf 13y agoThis does not appear to implement a monadic computation. A monadic computation can examine intermediate steps of the computation and perform flow control decisions, i.e. in Haskell: showFlowControl :: Person -> Maybe County showFlowControl p = do country <- personCountry p case country of USA -> do state <- state country county state SomethingElse -> do province <- province country provCounty province _ -> Nothing (The implied data structure is a bit bizarre, but, bear with me.) Unless I am misreading the code, this is the rather frequent misunderstanding that monads are just fancy chained method calls, which does not suffice to capture the monadic behavior. Note merely trying to stuff "if" statements into the calls won't be sufficient, because you must actually affect the chain that is called; either you call "state" or you call "providence" but you must decide that "in" the monadic computation, not in a mere method chaining. Mere method chaining is not monadic. There is, I believe, no clever way to implement monadic computations in Javascript without nested functions; every layer of the monadic computation nests, and in most languages this can not be hidden in any practical way. This is one of the major reasons monadic computations are impractical in most languages.
- dllthomas 13y agoFancy chained method calls are equivalent to do notation without any binds, which only needs Applicative. Monad is sufficient but not necessary. That said, this does appear to be a monad - they implemented bind correctly. Haskell: instance Monad Maybe where Nothing >>= _ = Nothing (Just x) >>= k = k x From the article: var Nothing = { bind: function(fn) { return this; } }; var Something = function(value) { return { bind: function(fn) { return Maybe(fn.call(this, value)); } }; }; It doesn't really use it right, for the most part, though (... I think; I only skimmed).
- jerf 13y agoI think bind is essentially correct too, but the failure to use it properly tends to suggest a level of understanding too small to be writing Yet Another Monad Tutorial. I've seen real JS monads, and I believe they are irretrievably ugly to use, with the need for nested functions.
- aaronem 13y agoHelpful aside: "Province", not "providence". The former is an administrative division of a larger polity; the latter is the grace of God, which could lead to some confusion when mentioned in this context.
- jerf 13y agoOops. No idea where that came from; I know better. Perhaps the backwards state of SomethingElse believes not only in the divine right of Kings, but the divine right of Dukes and perhaps even other nobility as well...
- aaronem 13y agoWell, if you assume both a peerage and a divine-right monarchy, then the divine right of peers seems more or less implicit, since they're created as such by the Crown, and a monarch anointed and guided by God would not fail to recognize who among her subjects is worthy of elevation. Subordination doesn't seem hard to handle in such a system; a duke's word, for example, would be God-given law save where it happened to conflict with the dictates of the Queen, an earl's likewise in relation to a duke, and on down the line, and presumably such conflicts could be handled by any noble of higher rank than the one found to be in conflict -- an earl, for example, might censure an unruly baron. …come to think about it, given a few global string replacements, haven't we just more or less reinvented both the British peerage and the Roman Catholic Church?
- cliveowen 13y agoSean Testes » A Gentle Intro to Gonads … Maybe? Sorry, I had to.
- agscala 13y agoIt seems like a good intro, but one of these days I would really like to see a monad explanation that doesn't use Maybe.
- lmm 13y agoI used Future as well as maybe - http://m50d.github.io/2013/01/16/generic-contexts.html http://m50d.github.io/2013/01/16/generic-contexts.html . I think it's important to give at least two or three examples, because the whole point of the idea of a monad is that it's an abstraction that several different datatypes conform to.
- FreezerburnV 13y agoWhat kind of example would you like to see other than Maybe?
- jerf 13y agoList is probably the minimal intro you can do without trapping yourself. It's too easy to implement something that can do the 1-or-0 elements in Maybe, but still isn't a Monad. You should also do a conditional, which I believe this code fails; that is, a monadic computation can examine the intermediate results and make flow-control decisions. The minimal example is probably something like: minimal :: Int -> [(Int, Int)] minimal i = do x <- [i, i + 1] y <- [i, 10 * i] if i `mod` 2 == 0 then return (x, y) else return (y, x) Running this in GHCi, I get: > minimal 2 [(2,2),(2,20),(3,2),(3,20)] > minimal 3 [(3,3),(30,3),(3,4),(30,4)] The evenness or oddness of the parameter controls which side of the tuple gets which element. It may still be possible to implement this without implementing a full monadic computation, but if you can pass this test using something like bind or join, you're probably at least on the right track.
- jerf 13y agoThis conversation is dead, but I've expanded on this at http://www.jerf.org/iri/post/2928 http://www.jerf.org/iri/post/2928 .
- Mithaldu 13y agoThis misses the more simple explanation of Monads: They're functions provided by the language core, that cannot be implemented in the language itself, because the language rules forbid it. This should be the first thing any of these introductions explain. Afterwards they can spend however long they like explaining all the rules for which Monads are solutions and how they get around them, safe in the knowledge that the reader will at least know why monads exist and why people are going to all these efforts.
- FreezerburnV 13y agoCan you give an example of a language rule which a Monad might implement? I sort of understand what you mean, but an example would help me (and maybe others?) form a more concrete understanding. And if at all possible, don't use Haskell being unable to mutate state as an example. Maybe an example for Javascript or Python?
- danidiaz 13y agoNullable types in C# "The predefined unary and binary operators and any user-defined operators that exist for value types may also be used by nullable types. These operators produce a null value if the operands are null; otherwise, the operator uses the contained value to calculate the result." http://msdn.microsoft.com/en-us/library/2cf62fcy.aspx http://msdn.microsoft.com/en-us/library/2cf62fcy.aspx They are useful to avoid repeated null checking. This can be modeled as a Monad with a notion of "failure". In Haskell that would be the Maybe monad. You would have to "lift" the arithmetic functions into it and work with "Maybe Int" values instead of "Int" values. An if we swapped the Maybe monad with the List (nondeterminism) monad, we could work with list of values and obtain a list of results for all possible combinations (instead of just one result or failure.)
- tel 13y agoHere's an interesting one: let's implement backtracking non-deterministic parsing as a Monad. If the code below is complex remember that it really is a near complete implementation of a backtracking non-deterministic parser from scratch using no libraries. We'll do it first without any mention of the M-word. A parser is the kind of thing that consumes a string and returns a parsed value along with some "leftover" string data. For instance, a digit parser would look like (in Haskell types) digitParse :: String -> (Int, String) digitParse ('1' : leftover) = (1, leftover) digitParse ('2' : leftover) = (2, leftover) digitParse ('3' : leftover) = (3, leftover) ... We can begin here and incrementally add more features. For instance, what happens when digitParse is applied to "foo". It ought to fail! To solve this, we'll introduce the Maybe (or Option) type digitParse :: String -> Maybe (Int, String) digitParse ('1' : leftover) = Just (1, leftover) ... digitParse ('f' : leftover) = Nothing ... Finally, if we have a parser which is sensitive to both apples and apricots then we might want to augment our return type with a notion of multiplicity of successes. We'll use a list for this and I want to note specially that we can now get rid of the notion of failure as represented by Maybe because we can use use the empty list ([]) to mean "parse failure". All together we can call this type a Parser. A Parser which attempts to parse a polymorphic type can be considered as well, so we'll leave that as a type variable newtype Parser a = Parser (String -> [(a, String)]) runParser :: Parser a -> String -> [(a, String)] runParser (Parser f) = f Now we can update the previous type `digitParse` digitParse :: Parser Int --- All by itself, Parsers aren't useful. We need to be able to combine them to make sense of a larger string. For instance, let's use the original notion of digitParser to build twoDigitParser twoDigitParser :: String -> ((Int, Int), String) twoDigitParser input = let (i1, leftover1) = digitParser input (i2, leftover2) = digitParser leftover1 in ( (i1, i2), leftover2 ) Not too bad. We could even use recursion to parse a whole list of digits. manyDigitParser :: String -> ([Int], String) manyDigitParser input = let (i, leftover) = digitParser input (is, leftover') = manyDigitParser leftover in ( i : is, leftover' ) To make this truly work we'd want to layer in the notions of failure and non-determinism afforded by the Maybe type and the list type, but this simple example should suggest that writing all of this stuff out would be a LOT of boilerplate. We'd like to build up to using monads to reduce all of that boilerplate. Ultimately we'll be able to define the fully non-deterministic `manyDigitParser` as manyDigitParser :: Parser [Int] manyDigitParser = do i <- digitParser is <- manyDigitParser return (i : is) Or even manyDigitParser :: Parser [Int] manyDigitParser = liftM2 (:) digitParser manyDigitParser --- Monads depend on Functors, so we'll begin there. Functor just means that if we have a type Parser ty1 we can transform it into a type Parser ty2. For instance, let's say I have a function that converts lists of Int to a single Int by gluing digits together. glueDigits :: [Int] -> Int A Functor over Parser would let me turn manyDigitParser into integerParser integerParser :: Parser Int integerParser = fmap glueDigits manyDigitParser To write it we need to define fmap polymorphically to just reach in modify the return type of our Parser. We'll destruct and reconstruct a Parser type to do this. instance Functor Parser where fmap modify (Parser parse) = Parser (\input -> let results = parse f in map (\(return, leftover) -> (modify return, leftover)) results ) Now `integerParser` is perfectly valid code and Parser is an instance of Functor. > :type fmap (+1) (undefined :: Parser Int) fmap (+1) (undefined :: Parser Int) :: Parser Int > :type fmap (\_ -> 3) (undefined :: Parser Char) fmap (\_ -> 3) (undefined :: Parser Char) :: Parser Int --- Now we get to the meat of things. We'll define a notion of Applicative, another superclass of Monad. Applicative just says two things-- 1. We ought to be able to make a constant parser--one which always succeeds, always returns some constant value, and doesn't consume any input constantParser :: a -> Parser a constantParser a = Parser (\string -> [(a, String)]) 2. We ought to be able to take a parser which returns a function and apply it to a parser which returns an input to that function as if they were regular values. We'll do so by running the function parser first and then the value parser and combining their results. In types that looks like apParser :: Parser (a -> b) -> Parser a -> Parser b apParser (Parser fparse) (Parser aparse) = Parser (\input -> [ (fun aval, aleftovers) | (aval, aleftovers) <- aparse fleftovers , (fun, fleftovers) <- fparse input ] ) The above syntax is just a list comprehension exactly like Python's. We use these two functions to form another typeclass instance for Applicative and reveal the generic names of constantParser and apParser instance Applicative Parser where pure = constantParser (<*>) = apParser --- Finally, we can move to the brunt of the problem: the Monad instance. Again, the entire goal here is just to abstract out the methods of sequencing parsers that we did manually at the very beginning. We'll do it first for the basic parser type. newtype BasicParser a = BasicParser (String -> (a, String)) Monadic sequencing is a little strange to grasp at first. Like Applicative we need a way to inject constants into BasicParser. instance Monad BasicParser where return a = BasicParser (\input -> (a, input)) ... But we also need to somehow consolidate those lets from above. To do this we'll write a function called bind (but written (>>=)) that has the type (BasicParser a -> (a -> BasicParser b) -> BasicParser b). BasicParser parser >>= continuation = BasicParser (\input -> let (a, leftovers1) = parser input (b, leftovers2) = runParser (continuation a) leftovers1 in (b, leftovers2) ) Notice how we had to use that exact same let construct to define the parser? That's why it gets abstracted away. We can do the same for the more general non-deterministic, failing parser instance Monad Parser where return = pure Parser parser >>= continuation = Parser (\input -> [ (b, leftover2) , (b, leftover2) <- runParser (continuation a) leftover1 , (a, leftover1) <- parser input ] ) --- So while that's a fairly large amount of abstract code, the end result is that we've embedded a fairly complex effect into our language—one that no reasonable language would have built in for you---non-determistic backtracking parsing. We can also centralize our optimizations of that parser into the monadic code and reap the benefits across the entire codebase. Furthermore, most of what I wrote above, while abstract, requires almost no creativity as it just comes from recognizing that our Parser monad is a certain, standard combination of the State and List monads.
- mbseid 13y agoI made a port of some Scala Monads to Javascript. Mostly, Option and Either. Makes for some really nice, and you won't find yourself doing (typeof foobar === "undefined"). https://github.com/mbseid/Monad.js https://github.com/mbseid/Monad.js
- vezzy-fnord 13y agoIs it bad if I find the null checks to be simpler in this particular scenario?
- NDizzle 13y agoI hope not, for my own sake!
- rogerallen 13y agoSuggestion for the author. Reformat the code of your translated example. I truly found it impenetrable until I formatted it more like idiomatic d3.js code. With this reformatting, I'll buy that it is less annoying and tedious than the original. console.log( Maybe(person) .bind(function(p) { return p["address"]; }) .bind(function(a) { return a["state"]; }) .maybe("State unknown", function(s) { return s; }) ); And then I realized that d3.js code is monads! (shout it like Charlton Heston in Soylent Green)
- dllthomas 13y agoI'm not sure this is actually using the fact that maybe is monadic - I think this much can be done with applicative functors. In order to really be monadic, you'd need to run the next bind inside the function passed to the previous bind.
- jasallen 13y agoSo, I don't get all the pain. There are a lot of 'rules' around monads, but conceptually they are widgets in a workflow. In imperative programming you say what you want to do and do it at the same time. With monads, you assemble a chain, just like dropping boxes on a workflow UI. Just like a workflow, you have 'defined' what to do, but not done anything with it yet. When you give it input, the chain performs the transforms you have defined for it, and you can pull out the result. Again, not a 'technical definition' but this paints a picture in people's heads that they can appreciate and follow it up with details. For serious learning, I recommend Eric Lipperts blog post on the subject: http://ericlippert.com/category/monads/ http://ericlippert.com/category/monads/