10 ms·
This sounds like a weaker form of algebraic effects, but it is still cool to see such a language and see what you can do with it.
by helix278 2y ago
This sounds like a weaker form of algebraic effects, but it is still cool to see such a language and see what you can do with it.
- levzettelin 2y agoI never understood "algebraic effects". But I understand the Hurl docs. Are "algebraic effects" basically the "toss" keyword from Hurl? If so, how are "algebraic effects" stronger than the "toss" keyword?
- ctenb 2y agoMore general than the toss keyword even. If you're interested in concrete examples, you might have a look at the koka documentation
- throw156754228 2y agoAs mentioned in another comment here, the unwind is expensive. That is, searching back through the stack finding the original call site that triggered the exception. How do algebraic effects handle that performance hit?
- fwip 2y agoYou can think of algebraic effects kind of like: From callsite Foo, I call out to a function with a known name, Bar, with one or more parameters. The runtime (or sufficiently smart compiler) searches upwards in scope, for a handler Baz that provides the function with that name. Baz's Bar is then called, with both the provided parameters, and, crucially, a function that is "the rest of Foo." So, an implementation of Exception with effects would ignore the resume, and look something like: define_effect Exception { // only one function provided by Exception, but could have more throw(String) } define_handler printExceptions { throw(msg, resume_func): { println(msg) } } define_func getPage(url) { request = http.get(url) if not request.ok { throw("Could not download") } return page } // main entrypoint withHandlers printExceptions { page = getPage("https://cheese.com") println(page.text) } But you could also write an "on error resume next" handler for Exception. Exceptions thrown with this handler would be equivalent to toss. (in real life you'd probably write a different effect, rather than re-using the Exception/throw effect): define_handler onErrorResumeNext { throw(msg, resume_func): { println(msg) // YOLO, call it anyways withHandlers onErrorResumeNext { resume_func() } } } withHandlers onErrorResumeNext { page = getPage("https://cheese.com") println(page.text) // Probably uninitialized - a better :P } Breaking away from the Exception example, two cool examples are: // Stream - potentially infinite and/or asynchronous iterables // basically just like python generators define_effect Stream { emit(item) } define_handler toList(accumulator) { emit(item, resume_func): { if item == nil { return accumulator } else { accumulator.append(item) withHandlers toList { resume_func() } } } } define_handler take(how_many) { emit(item, resume_func) { if how_many == 0 { emit nil } else { withHandlers take(how_many-1) { resume_func() } } } } define_func fib_stream() { a, b = 0, 1 loop { emit a a, b = b, a+b } } // Usage first_five = withHandlers toList { withHandlers take(5) { fib() } } println("The first five fibonacci numbers are", first_five) And this one I'm just going to lift from the Unison documentation [1]: Each.toList do a = Each.range 0 5 -- beginning of resume_func f_A b = each [1, 2, 3] -- beginning of resume_func f_B guard (a < b) -- beginning of resume_func f_C (a, b) -- yields [(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)] Note that this has the same semantics as: results = [] for (a = 0; a < 5 ; a++) { // for each a, call f_A(a) foreach b in [1, 2, 3] { // for each b, call f_B(b) if not a<b { continue } // if a<b, call f_C(a, b) results.append((a, b)) } } And it is possible to do this, because these resumable functions can be resumed an arbitrary number of times - it's up to the handler. They also can pass parameters back to the resume_func. Exceptions/hurl = exactly 0 resumptions toss = exactly 1 resumption effects = 0..many resumptions [1]: https://share.unison-lang.org/@unison/base/code/releases/3.5.0/latest/types/abilities/Each https://share.unison-lang.org/@unison/base/code/releases/3.5...
- levzettelin 2y agoI see. Many thanks for this! Sounds a bit like a mixture of coroutines and dynamic scoping. Very interesting!