6 ms·
You've changed my mind on adding special syntax for `async`, generators, and results (`?`). I previously thought it was a big mistake given that these all gener
by harpocrates 9y ago
You've changed my mind on adding special syntax for `async`, generators, and results (`?`). I previously thought it was a big mistake given that these all generalize as monads (granted, there is some ground to be covered before such an abstraction would fit in Rust).
What I hadn't considered: specialized syntax leads to better error messages for the most common cases. That's probably quite a good thing, especially since descriptive error messages make me much more productive. (Not to mention that specialized syntax also acts as a type annotation of sorts too; a common problem I have in Haskell is figuring out _which_ monad a certain `do` block is using.)
- gilmi 9y ago> a common problem I have in Haskell is figuring out _which_ monad a certain `do` block is using. Does type signatures not help in that case?
- harpocrates 9y agoYes, when people use them. There is no really elegant way of adding a type annotation to a `do` block. (do x <- pure 1 y <- pure 5 pure (x + y)) :: Maybe Int It isn't uncommon to have to scan the `do` block for some statement that constrains the monad somehow. This is a known and discussed problem, so I'll refer you to the article usually reference around this issue: https://wiki.haskell.org/Do_notation_considered_harmful https://wiki.haskell.org/Do_notation_considered_harmful
- nogenerics123 9y agoMany times do blocks are passed into functions so the monad type can be looked up from the function definition. flip runStateT s $ do ... To find the type of this do block I don't have to scan it I just have to look up the definition of runStateT. If I have to add an explicit annotation to a do block it's simple enough to separate it into another function. justSix :: Maybe Int justSix = do x <- pure 1 y <- pure 5 pure (x + y) I read the article and couldn't find the part where they said that type inference is a problem with do notation.
- Rusky 9y ago> (granted, there is some ground to be covered before such an abstraction would fit in Rust) I would claim that such an abstraction is fundamentally incompatible with Rust. Not only does Rust lack the means to write a Monad trait on which to build do-notation, but even given HKT there's no single type signature that the various monad instances would fit. `Result` and `Option` are type constructors while `Iterator` and `Future` are generic traits; some instances require `Fn` or `FnMut` while others require `FnOnce`. And even assuming those problems could be solved, the way do-notation interacts with control flow is not composable with idiomatic Rust. Nested closures prevent the use of return/break/continue and imperative loops; Haskell doesn't have those features so people just lift their functional counterparts into monads. Monad transformer stacks make the situation even worse- they are a pain to compose even with each other, let alone imperative code. If you want a unifying mechanism for this stuff in Rust it's gonna need to be fundamentally more powerful than monads. Scoped continuations, maybe? Certainly nothing that looks like >>=.
- andrewaylett 9y agoYou don't need do-notation to use Monads, that's just useful for building up a lazy-evaluated data-structure describing what IO to perform. Java and Javascript, for example, have been gradually introducing monadic concepts into their ecosystems. Java collections and Futures grow 'of' and 'flatMap', JS Promises aren't quite purely monadic but have 'resolve' and 'then'. So it's perfectly possible for an imperative language to use monadic features to implement async operations. But much as I enjoy using JS Promises (really!) I prefer ES6 async/await, even though it's just syntactic sugar. Not least because even fairly straightforward-looking async/await code can desugar to something you wouldn't want to try to read by hand.
- Rusky 9y agoI'm... not sure what you're getting at here. Rust is chock-full of "monadic concepts" like that, including the entire sets of `Iterator` and `Future` combinators. (Note that even without do-notation these have the composability problems I mention- you can't, for example, `break` out of a loop from within a `.then` callback; you have to break the loop down and reimplement it yourself with recursion. This is what the async/await "sugar" helps with- makes normal control structures compose with async code.) What I'm talking about is a single Monad abstraction to tie these all together. I mention do-notation because it's one example of something you would built on top of such an abstraction, and one way to bring together the seemingly-disparate collection of `?`/`yield`/`await!`. In Haskell, for example, you can use do-notation not only for IO, but also for async code, and list comprehensions, and early returns, and a host of other things.