I've spent some time thinking about language design recently. Coloured functions have a surprising number of downsides:
- They're sometimes much too explicit. When writing a complicated generator, I don't necessarily want to annotate every call to a sub-generator with `yield*`, especially if I need to drill that annotation through wrapper functions which aren't really generators themselves.
- Colours show up everywhere. If you have several functions with a `context: GodObject` parameter, then that's a function colour. Most third-party code will be unable to forward a `context` argument to a callback, so you'll have to manually smuggle it in using closures instead.
- Different "colour channels" don't compose nicely with one another. Even though JavaScript ES2017 provided both async functions and generators, ES2018 had to add multiple new pieces of syntax to permit async generators.
- It's normally impossible to write code which is generic over a function's colours. For example, if you have `function callTwice(f)` in JavaScript, you'd need a separate `async function callTwiceAsync(f)`, and a `function* iterateTwice(iterable)`, and an `async function* iterateTwiceAsync(iterable)`, despite the fact that all of those functions are doing the same thing.
Several small languages are experimenting with algebraic effect systems [1], which would make all functions colourless. If JavaScript had this feature, the syntax for defining and calling functions would be the same, no matter whether you're dealing with a normal function, a generator, or an async function. No more `async`, `await`, `function*`, `yield`, `yield*`, `for-of`, `for await`...
This can make everything too implicit and vague. The state of the art is for algebraic effect systems to be implemented in a statically-typed language, which then uses type inference to quietly tag all function types with their colours. This means that the compiler can enforce requirements like "you can only call generators within a scope which is prepared to collect their results", but that rule wouldn't prevent you from wrapping a generator in `callTwice`.
> Most third-party code will be unable to forward a `context` argument to a callback
Having a context argument which is passed to the callback has been par for the course in all cases I've seen, but I've mostly been in C land and related areas.
Though, if you have closures, should you still prefer a context param?
> If JavaScript had this feature, the syntax for defining and calling functions would be the same, no matter whether you're dealing with a normal function, a generator, or an async function.
How would one reason about performance and possibly concurrency in such languages?
In a language with algebraic effects, questions like "how should I make this `CancellationToken` available to these forty small functions?" simply go away :-)
It's ambient state, just like a `catch` block, and so you can make it ambiently available to a whole tree of function calls. The outcome is the same as passing around a context argument by hand, and it can have equally strong type-checking, but the compiler handles all of the boilerplate for you.
To answer your second question: I agree that, in a dynamic language like JavaScript, making all of these language features implicit could be too costly. I'm more optimistic about effect systems in statically-typed languages.
Ah, like Python objects then. Which is unfortunate, because that's one of my main dislikes with languages like Python.
I was imagining more along the lines of (on-the-spot pseudo-code)
using [x := foo(...)]
{
bar();
output();
}
func bar()
{
x = x + 1
}
where bar() and output(), or something they call in turn, can reference x and it would be a compile-time error to use bar() or output() in cases where they couldn't resolve x. Or something more along those lines.
Anyway, cheers, always fun to ponder these things.
Sorry, the Wikipedia article isn't very helpful. Your example is the correct one.
The killer feature is that you can shadow a dynamic variable, changing its value for the duration of a dynamic scope, in the same way that an inner `catch` handler overrides an outer one.
For example, you could have a dynamic variable which tells the `print` function where to send its text output. The variable's value would probably default to stdout. By temporarily assigning a string-stream to that variable, you could collect an arbitrary function's text output into a string instead. Regardless of where the text is going, any function which produces text could still use the convenient global `print` function.
Right. The closest thing I'm familiar with is the dynamic context used in Serilog[1], where you add log properties to the current scope, which gets added to each log line generated in that scope, and removed once the scope exits.
Of course, no compile-time checking there, so the combination sounds indeed quite useful.
- They're sometimes much too explicit. When writing a complicated generator, I don't necessarily want to annotate every call to a sub-generator with `yield*`, especially if I need to drill that annotation through wrapper functions which aren't really generators themselves.
- Colours show up everywhere. If you have several functions with a `context: GodObject` parameter, then that's a function colour. Most third-party code will be unable to forward a `context` argument to a callback, so you'll have to manually smuggle it in using closures instead.
- Different "colour channels" don't compose nicely with one another. Even though JavaScript ES2017 provided both async functions and generators, ES2018 had to add multiple new pieces of syntax to permit async generators.
- It's normally impossible to write code which is generic over a function's colours. For example, if you have `function callTwice(f)` in JavaScript, you'd need a separate `async function callTwiceAsync(f)`, and a `function* iterateTwice(iterable)`, and an `async function* iterateTwiceAsync(iterable)`, despite the fact that all of those functions are doing the same thing.
Several small languages are experimenting with algebraic effect systems [1], which would make all functions colourless. If JavaScript had this feature, the syntax for defining and calling functions would be the same, no matter whether you're dealing with a normal function, a generator, or an async function. No more `async`, `await`, `function*`, `yield`, `yield*`, `for-of`, `for await`...
This can make everything too implicit and vague. The state of the art is for algebraic effect systems to be implemented in a statically-typed language, which then uses type inference to quietly tag all function types with their colours. This means that the compiler can enforce requirements like "you can only call generators within a scope which is prepared to collect their results", but that rule wouldn't prevent you from wrapping a generator in `callTwice`.
[1]: https://github.com/ocaml-multicore/ocaml-effects-tutorial