6 ms·
I'll bite. What are useful uses of monads, other than implementing exceptions, async, state and IO? Granted, it's aestethically pleasing to have a really tiny c
by pacala 13y ago
I'll bite. What are useful uses of monads, other than implementing exceptions, async, state and IO? Granted, it's aestethically pleasing to have a really tiny core on which to layer more sugar, but sometimes using the language as-is suffices.
The mother of all monads is Cont (http://blog.sigfpe.com/2008/12/mother-of-all-monads.html http://blog.sigfpe.com/2008/12/mother-of-all-monads.html), but that belongs in the bowels of the compiler.
- Peaker 13y agoContinuations are useful monads too (for implementing coroutines or control features like Python's "with", etc). As are parsers, uniqueness, randomness, non-determinism, readers, writers, regions, resource management, local-state threads (ST monad), probability distributions, software transactional memory, .... Another nice thing is composing various monads to build a custom one for the purpose you need. For example: StateT S1 (EitherT Err (State S2)) a Will be a stateful computation that always has yields a new S2 value, even in exceptions (preserving any modifications up to the exception) but only yields an S1 in the case of success. This is just one of infinite possible compositions of monad transformers. If you hard-code a certain ambient monad into your language, you won't be able to use monads as a DSL for the use case you have at a certain time. For example, in the application I'm developing now, I use a "Transaction" monad that guarantees that my key/value store transactions cannot do any IO or anything other than read keys and write keys. As a bonus, that means I don't even need transactionality from the underlying data store -- I can "revert" or "commit" by implementing the Transaction monad as a state tracker of all the changes on top of a hidden IO layer that exclusively allows reading keys. This also means I can implement useful transactional primitives such as "forkScratch" which allows me to fork the transactional state, run an action in that forked state, discard its transactional side effects, and keep its result. I use this to "try out" a transaction, see if it would work (in my context, type-check the new state and discard it) without actually having any effect. tl;dr: There isn't a finite set of useful monads you can bake into the language. There are infinite compositions of useful monads.
- pacala 13y agoThe point is to not (re)invent little languages all the time, but to use the actual language. For coroutines, see https://sites.google.com/site/gopatterns/concurrency/coroutines https://sites.google.com/site/gopatterns/concurrency/corouti.... The parser combinator is also interesting. A parser combinator is merely a function that takes an input pointer and produces a list of matches. One can implement this in Go (or any programming language I know of) without agonizing over the syntax used to chain combinators. Essentially, the type machinery necessary to implement and use monads is a tradeoff. One can implement the code in pretty much any language one wants. The only point in contention is how many invariants are enforced by the type system vs. how much type system wrestling one has to deal with. I lack the empirical evidence that, for example, "monad that guarantees that my key/value store transactions cannot do any IO" is any better than a comment "// Transactions never do IO. To perform perform IO use package TIOBridge.". But I have a nagging feeling that adding logging or exceptions in this context is quite a bit more convoluted than "import log; log.info(...)" or "panic(...)".
- Peaker 13y agoEssentially, you're saying that the point is not to use DSL's. The benefits of DSL's are explained everywhere, so I don't think I need to repeat them here... Parser combinators are not what you said they are -- and you won't be able to write the kinds of things you can in Haskell. e.g, to parse 10 comma-followed-by-int: replicateM 10 (parseComma >> parseInt) How would you implement that in Go? Even if you violate DRY and re-implement replicateM in every monadic context I don't think Go will be able to encode anything similar in power to parser combinators. > But I have a nagging feeling that adding logging or exceptions in this context is quite a bit more convoluted To add exceptions, I use an EitherT transformation on my Transaction monad. To do a debug log, you just insert an impure debug log as you would in a non-pure language. To insert an actual production log you would use a Writer that accumulates the logs in order to eventually write them outside the transaction context. Otherwise, aborted transactions will also have the production logs. If you want these semantics, add logging capabilities to the transaction monad. Otherwise, you get nice guarantees about what can't happen.
- aninhumer 13y agoMonads with do-notation also provide a really powerful basis for creating custom DSLs with very varied semantics, which still allow you to use all the standard library functions for working with monads. I suspect various other abstractions could be similarly powerful with appropriate syntax sugar.