Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What does algebraic effects mean in FP?

Ref:

  • http://www.eff-lang.org/handlers-tutorial.pdf

  • https://www.microsoft.com/en-us/research/wp-content/uploads/2016/08/algeff-tr-2016-v2.pdf

  • https://github.com/matijapretnar/eff

I have searched a lot of links, but it seems that no one could explain it specifically. Could anyone give some code(use javaScript) to explain it?

like image 449
SmallTown NE Avatar asked Apr 03 '18 09:04

SmallTown NE


1 Answers

What is an Algebraic Effect?

TL;DR: In short, Algebraic Effects are an exception mechanism which lets the throwing function continue its operation.

Try to think of Algebraic Effects as some sort of try / catch mechanism, where the catch handler does not just "handle the exception", but is able to provide some input to the function which threw the exception. The input from the catch handler is then used in the throwing function, which continues as if there was no exception.

Some sample pseudo code:

Let's consider a function which needs some data to perform its logic:

function throwingFunction() {     // we need some data, let's check if the data is here     if (data == null) {         data = throw "we need the data"     }     // do something with the data } 

Then we have the code that invokes this function:

function handlingFunction() {     try {         throwingFunction();     } catch ("we need the data") {         provide getData();     } } 

As you see, the throw statement is an expression evaluating to the data provided by the catch handler (I used the keyword provide here, which afaik does not exist in any programming language of today).

Why is this important?

Algebraic Effects are a very general and basic concept. This can be seen by the fact that many existing concepts can be expressed in Algebraic Effects.

try/catch

If we had Algebraic Effects but no Exceptions in our favorite programming language, we could just omit the provide keyword in the catch handler, and voilà, we would have an exception mechanism.

In other words, we would not need any Exceptions if we had Algebraic Effects.

async/await

Look again at the pseudo code above. Let's assume the data which we need has to be loaded over the network. If the data is not yet there, we would normally return a Promise and use async/await to handle it. This means that our function becomes an asynchronous function, which can only be called from asynchronous functions. However, Algebraic Effects are capable of that behavior too:

function handlingFunction() {     try {         throwingFunction();     } catch ("we need the data") {         fetch('data.source')             .then(data => provide data);     } } 

Who said that the provide keyword has to be used immediately?

In other words, had we had Algebraic Effects before async/await, there would be no need to clutter up the languages with them. Furthermore, Algebraic Effects would not render our functions colorful - our function does not become aynchronous from the language viewpoint.

Aspect-Oriented Programming

Let's say we want to have some log statements in our code, but we don't yet know which logging library it will be. We just want some general log statements (I replaced the keyword throw with the keyword effect here, to make it a bit more readable - note that effect is not a keyword in any language I know):

function myFunctionDeepDownTheCallstack() {     effect "info" "myFunctionDeepDownTheCallstack begins"      // do some stuff      if (warningCondition) {         effect "warn" "myFunctionDeepDownTheCallstack has a warningCondition"     }      // do some more stuff      effect "info" "myFunctionDeepDownTheCallstack exits" } 

And then we can connect whichever log framework in a few lines:

try {     doAllTheStuff(); } catch ("info" with message) {     log.Info(message); } catch ("warn" with message) {     log.Warn(message); } 

This way, the log statement and the code that actually does the logging are separated.

As you can see, the throw keyword is not really suited in the context of the very general Algebraic Effects. More suitable keywords would be effect (as used here) or perform.

More examples

There are other existing language or library constructs that could be easily realized using Algebraic Effects:

  • Iterators with yield. A language with Algebraic Effects does not need the yield statement.
  • React Hooks (this is an example of a construct at the library level - the other examples here are language constructs).

Today's support

AFAIK there are not many languages with support for Algebraic Effects out of the box (please comment if you know examples that do). However, there are languages which allow the creation of algebraic effects libraries, one example being Javascript with its function* and yield keywords (i.e. generators). The library redux-saga uses Javascript generators to create some Algebraic Effects:

function* myRoutineWithEffects() {     // prepare data load     let data = yield put({ /* ... description of data to load */ });     // use the data } 

The put is an instruction that tells the calling function to execute the data load call described in the argument. put itself does not load anything, it just creates a description of what data to load. This description is passed by the yield keyword to the calling function, which initiates the data load.

While waiting for the results, the generator routine is paused. Then, the results are passed back to the routine and can then be used there after being assigned to the data variable. The routine then continues with the local stack plus the loaded data.

Note that in this scenario, only the calling function (or the code that has a reference to the generator) can "serve" the algebraic effect, e.g. do data loads and other things. So it is not an algebraic effect as described above, because it is not an exception mechanism that can jump up and down the call stack.

like image 88
cheesus Avatar answered Sep 18 '22 14:09

cheesus