5 ms·
Another reason monads are cumbersome in other languages is that other languages use lazy evaluation. If you consider the naively implemented function skip
by shrughes 18y ago
Another reason monads are cumbersome in other languages is that other languages use lazy evaluation. If you consider the naively implemented function
skipMany :: Parser a -> Parser ()
skipMany p = (p >> skipMany p) <|> return ()
and then consider how you might write it in some other language, you'll note that the translation, strictly evaluated, gives infinite recursion. To practically work with monadic computation requires some form of non-strict evaluation. Another, less catastrophic example would be the following.
printNTimes :: Int -> String -> IO ()
printNTimes 0 s = return ()
printNTimes n s = putStrLn s >> printNTimes (n - 1) s
Strictly evaluated, that would build up Theta(n) memory usage.
For this reason, the more computation-oriented monads cannot be used practically in other languages without added weight to avoid strict evaluation.
Ironically, it's the non-monadic uses that cause the problems -- those that don't need the full power of 'bind', i.e. uses where the monad's user doesn't pass return values from previous actions to future actions. When you pass a return value, you end up needing a lambda expression, which cuts off strict evaluation. When you only use them in ways that applicative functors can be used (which often applies for parsers), that's when you suffer. It's not the end of the world; there certainly are workarounds, but that requires adding explicit promises everywhere, which is just a pain.
- olavk 18y agoThe bottom line is that monads are not practical without some kind of syntactic sugar like do-notation. Without that, you have to write operations as a chain of nested lambdas, which becomes very ugly very quickly. This is probably why monads haven't taken off in any language beside Haskell.
- chandler 18y agoThe first example, naively translated to scheme, results in: (define (skip-many a) (or (and (a) (skip-many a)) (void))) This is not infinite recursion. The Parser monad used in the Haskell version is simply a stream dressed in monadic clothes (and even C has streams a-la FILE*). As for the second example, the naive translation to scheme is: (define (print-n-times n s) (cond ((zero? n) (void)) (else (put-str-ln s) (print-n-times (sub1 n) s)))) Here, both Haskell & scheme demonstrate constant memory usage; not because of Monads, or strict/lazy evaluation, but because of tail recursion. Also, in your first sentence, I think you meant "strict".
- shrughes 18y agoNeither of your translations convert your code to anything that has to do with monads, and your first translation is just plain broken. Your print-n-times doesn't return an action---it's an imperative function that performs an action. The naive translations to Scheme would be as follows: (define (skip-many p) (<|> (>> p (skip-many p)) (return 'nothing))) (define (print-n-times n s) (if (= 0 n) (return 'nothing) (>> (put-str-ln s) (print-n-times (- n 1) s)))) In order to fix the infinite recursion in my first example, the parsing framework I made in Scheme had a macro `thunk-parser` (actually, it had a shorter, stupid-sounding name) which for all intents and purposes just wraps the expression in a (lambda ()). (define (skip-many p) (<|> (>> p (thunk-parser (skip-many p))) (return 'nothing))) Also, both your examples rely heavily on non-strict evaluation. 'and' and 'or' are both macros that non-strictly evaluate expressions.
- chandler 18y agoAgreed; neither of the translations use monads, because monads are a semantic hand-job proscribing order within a declarative language. However, Scheme is not a declarative language, therefore order is implied in the structure, therefore monads aren't necessary. >> Your print-n-times doesn't return an action... Nope, look again. It returns the results of applying `void'. Who knows what (void) may return in a loosely-typed language? >> ...it's an imperative function that performs an action. Well, considering Scheme is an imperative language with side-effects, it's hard to dodge such criticism. >> The naive translations to Scheme would be as follows... That's not a translation from Haskell to Scheme, that's writing Haskell using a prefix syntax. >> Also, both your examples rely heavily on non-strict evaluation. 'and' and 'or' are both macros that non-strictly evaluate expressions. I went back and re-read your original post: >> ...you'll note that the translation, strictly evaluated, gives infinite recursion. Mea culpa--I completely missed your point that translating from a turing complete language to a non-turing complete language might have some pitfalls.
- shrughes 18y agoThen maybe I'm misunderstanding the point of your reply. I was pointing out how monads are inconvenient to use in strictly evaluated languages. Why did you follow up with an example about how not using monads is convenient?