There's a lot to cover since I last wrote something, but some things have already been addressed by others in this thread.
@QuinceyMorris this case, there has to be a wait of some kind and the wait cannot block the thread on which the code is running. A monad solution doesn't intrinsically satisfy that requirement, so you still need compiler magic (e.g. coroutines) to make this possible.
I agree insofar as I see the need to be able to de-sugar things. As I said in my original proposal:
Would calling throwing functions without try now be legal? After all, they would simply return a Result then. Under which circumstances should there be a compiler warning and when should it be fine? This is mostly a discussion about communicating the intended use, at the type level there is no problem.
The real question is how we prevent confusion. For example, if await f(a) outside an async function just returned the monad that has been used to implement async-await, the g(b) simply wouldn't compile for type reasons. But would everyone understand why that is? This is something that really needs some further discussion as the idea matures.
@sveinhal I see your point. However, what is the likelihood for programmers that want to get things done to over-use that feature? I mean, Java provides annotations and a rich reflection API (and in a restricted form, Swift does too now), but how many frameworks use that in a way that one could say these uses aren't justified? Sure, people are going to experiment with the new feature - as they have done with property wrappers. I would even encourage them to! But only few real use cases actually stick, and most of the time these are very much justified. From what I can see, the Swift community has been very responsible with new language features so far.
Your question regarding other keywords has been answered by @pthariensflame, I hope that helps.
@Max_Desiatov Sorry, I should have mentioned Haskell style do-notation and for comprehensions known from other languages. However, in these designs, all monads just get the same 'syntactic sugar'. Also, the do-model might fit the state monad or asynchronous monads, but is it a good fit for arrays for instance? Conversely, for comprehensions are great for async stuff and arrays, but how am I supposed to parse the reader monad with for comprehension?
The keyword-based variation that treats monads as effects is actually something that is currently actively explored, for example by the experimental Eff programming language, but there are also Scala frameworks. I personally like the approach because the keywords kind of tell you what is going on (if you read the documentation).
I agree though that I should have mentioned the other approaches. If this idea matures to the point that I make an official proposal (as far as I understand the intention of the Pitches section, this is more about testing the waters), I will put these in the 'alternatives considered' section.
Circling back to @adamkemp :
I'm honestly not sure what problem this proposal is trying to solve. My original question is "where is the code that actually controls the compiler transform, and how does this feature allow that to be extended?" I haven't seen an answer to that question so far. How does using monads solve any of the actual challenges with coroutines or async/await specifically? And if it doesn't solve those challenges then what does it solve?
Ok, let's try again.
My concern with the async-await feature that is sometimes requested is how it would fit into Swift from a syntactical point of view and when it comes to the way it is implemented. I mean, there were times when error handling wasn't a feature that was syntactically supported in most languages. Async-await is another feature that many languages adopted only way later. When people see these concepts, they often think that they were always around, but there really is a progression. Say, in 10 years we get another effect and in 20 years yet another one and say each of these features had their own syntax (like in Kotlin where async functions look very differently from throwing functions) and the poor people implementing the feature had to inject the new syntax and how it should be handled every time. That can lead to a lot of confusion for developers and a less clear language design.
What I propose is simply a general tool to write those effects. Obviously, each monad still has to be implemented - but injecting them into the language in a clear and readable way without long map and flatMap chains is then simple.
Note that I changed the title to 'An extensible Syntax for effects like e.g. async-await'. I haven't touched the specific challenges of async-await here much, other than delivering a oversimplified implementation of promises and referring to RxSwift. Since async-await is a feature that many people would want to see, I would like the new syntax feature to ship alongside async-await (and it can be implemented with monads, just like any effect) which would showcase the generality and simplicity of the monadic approach.
@all There's another monad that just came to my mind: randomness. In Haskell, all functions are pure - meaning that the same input always produces the same output. To express randomness, they have a monad that in essence provides some primitive operations to produce random results, but forcing you to tell users of that function that you do random stuff via the type system. In Swift, you can always do random stuff freely (just like you can do IO or asynchronous stuff using GCD or even manipulate references), but introducing a random monad and keywords like 'random/flip/rerandom' could cause people to consider freely doing random stuff a code smell. Just like the introduction of the IO monad and keywords like 'io/show/reIOs' could make free IO actions a code smell.
Speculation: I could actually even imagine splitting Haskell's IO monad in Swift into an Input monad and an Output monad. If you write a controller class and create a delegate protocol for that, you could then specify that delegate methods may only do outputs, not inputs, thereby promoting unidirectional data flow at the type level.