An extensible Syntax for effects like e.g. async-await

In fact, they have optimized the shit out of the standard library ;) Swift is really pretty high performance if you know what you are doing.

For a proposal, I guess a Swift implementation should be enough? Also, what do you think about above code example?

The point is that in order to do a transform like that you have to be aware of the AST. It's not a simple transformation into a chain of calls, and it can't be done in a way that's generic for any effect-like-thing. To implement async/await in general you need compiler support. You need code generation that's aware of the AST, and you need to be able to do things like hoist locals into fields of a synthesized object. This is the kind of stuff that the compiler does when you use a closure. You couldn't add closures to the language without compiler support, and you're not going to be able to add async/await without specific compiler support even if we had your proposed feature.

I also want to be pedantic here for a second:

You can't do "async/await" with libraries. You can do asynchronous programming with libraries, and there are plenty of options available. async/await is a syntax. It's inherently a language feature. You can make async/await interoperate with asynchronous programming libraries (like C# does), but you can't build async/await as just a library.

Similarly, C# had IEnumerable for a very long time before yield return came around. You couldn't build yield return as a library feature, though. It's a compiler feature that interoperates with an existing library feature. You could implement everything you can do with yield return without that language feature, but it was a huge pain to do so. The language feature made the code simpler and yet just as powerful as before.

That's what async/await does. It turns complex code into easier to understand code. If you try to write that simple throwing example with a while loop using callbacks it gets really complicated and hard to understand, and yet the async/await version is just as clear as the synchronous version would be and does the same thing. It's simpler and yet just as powerful. That's what we want, and we can't get it by just using monads.

1 Like

I'm not sure what more generality you could want (assuming that the above code transformation is correct now). Applying it to general monads is the easiest step.

So, I'll try again.

In general, if you have some function with an effect (indicated by the keyword you have to use), you would call flatMap:

func foo(_ x: X) arbitrary -> Y{
...
y = arbiTry bar(x)
...
}

is

func foo(_ x: X) -> Arbitrary<Y>{
bar(x).flatMap{localX in ...}
}

Any variable above flatMap that is needed below would have to be captured.

The problem you describe arises when the effect is thrown from some local scope, like some while clause:

func foo(_ x: X) -> Arbitrary<Y>{

while ... {

bar(x).flatMap[//indicates what the scope *should* be

}//ouch

]//flatMap only ends after while

}

As indicated above, one would have to re-write the while loop, possibly in a recursive way. Generalizing your above code example:

func foo(base: Int) -> Arbitrary<Int>{
var result = base

if result < 0{
arbitraryThrowing().flatMap{//needs to contain *all* branching logic of while
result = $0//could be optimized by just passing $0 to arbitraryCaller
return arbitraryCaller(base: result)//recursion instead of while loop
}
}
else{
return .pure(result)
}
}

I am sure if we discuss this long enough, we figure out the general rules for all possible code transformations. Swift doesn't have too many control flow statements, and they can partially be reduced to other control flow statements. I have no idea how to actually write code that does code transformation, however I think even I could figure out a correct set of rules to do that in a way that it can be actually implemented. I mean, other languages (Eff!) have done something very similar.

To reiterate: monads are nothing else than continuations with type-specific invocation semantics. Any code you could write to make async/await syntactically possible (and it really only is syntax, not semantics) just needs to be slightly generalized in order to make really arbitrary monadic effects possible - and then you have a huge extensible feature almost for free.

There is no inherent difference between what needs to be done to make your above code example work and making, e.g., the following work:

var result = -1
while result < 0{
result = log foo()//writer monad
}
return result 

or

var result = -1 
while result < 0{
result = read foo()//reader monad that injects implicit arguments to a function 
}
return result 

or

var result = -1
while result < 0{
result = maybe foo()//optional monad. enclosing function may or may not return a value, depending on the success of this call to foo
}
return result

or

var result = -1
while result < 1{
result = interpret foo() //Free monad that can be used to inject arbitrary semantics 
}
return result 

All of these have an obvious operational semantics that could be achieved by having the compiler rewrite that a little bit - but structurally in the same generic way.

Hello Markus,

First of all, please accept my apologies for bumping your topic after a week or so. I realise that much has happened in the intervening time with regard to your suggestion.

In any case, I found your post fascinating, and would like to let you know that it prompted me on to further reading. I was intrigued to learn more about this idea of 'Algebraic Effects', and some of the people who have been involved in its development and discussion over recent years are nothing less than luminaries in their field.

In particular, I have stumbled upon a pair of articles which discuss a generalised implementation of this idea, with the bold program of unifying notions such as exceptions, state resumption, iterators, domain specific effects and, yes, even asynchronous programming.

These papers provide a formal discussion, as well as an implementation example (in Koka).

I thought this may interest you, and wish you all the very best.

From Monads to Effects and Back
Niki Vazou and Daan Leijen:

Algebraic Effects for Functional Programming
Daan Leijen, 2016:

6 Likes

Ahhh, thanks! Koka was that other experimental language that I forgot about. I remember a rather enlightening talk about that topic on youtube, but I can't find it right now.

Maybe the YouTube video you mean is the one in this post from another thread?

1 Like

That does look familiar, thanks.

So, this thread started accumulating some sources that suggest that in principle, this whole thing might actually be possible. All that was really suggested here are a swifty syntax to create algebraic effects.

The questions really are: how desirable would such a feature be and how to move forward?

1 Like