Li Boxiu


A Developer


This post is a proposal of implementing Algebraic Effects in Pivot Lang. It will cover the propose syntax and transformation rules to translate the effects into generators.

Syntax Proposal

Define an Effect Type

A effect type should be defined just like normal function type.

effect effect1: ||i32|=>void, u32| => void;

The effect type effect1 takes two parameters, the first one is a resolver that takes no i32 and returns void, the resolver is something like return statement in a function, and its argument is the value that the effect will return, it’s return type is always void. From the second parameter, they are the arguments that the effect will take.

The effect type can have generic parameters.

effect effect2<T>: ||T|=>void| => void;

Use an Effect

When a function uses effects is called, the caller should set up handlers for the effects in advance.


effect effect1: ||i64|=>void, i64| => void;
fn fnUsingEff1() void {
    let re = fnUsingEff2();

    perform effect1(re);
    return;
}

fn fnUsingEff2() i64 {
    perform effect1(1);
    return 100;
}

fn eff1Handler(resolve: ||i64|=>void, a:i64) void {
    resolve(a+1);
    return;
}

fn main() void {
    handler effect1(a:i64) {
        eff1Handler(a);
    } handle {
        let a = fnUsingEff1();
        println(a);
    }
    return;
}

Transform Effects into Generators

The effect system can be transformed into a generator system.

The above code can be transformed into the following code:


var effect1 = 1;
var eff_ret = 10086;


fn main() void {
    executeGenerator(fnUsingEff1());
    return;
}

gen fn fnUsingEff1() Iterator<(i64, |*||=>void,*||=>void| =>void)> {
    let retgen2 = 0;
    let generator = fnUsingEff2();
    while true {
        let nx = generator.next();
        if nx is None {
            break;
        }
        let nx_v = nx as (i64, |*||=>void,*||=>void| =>void)!;
        match nx_v {
            (10086, f) => {
                let wrapped_f = |iter:*||=>void,handler:*||=>void| => {
                    let real_handler = unsafe_cast<|||=>void|=>void>(handler);
                    let ret_handler = |resolve:||=>void, ret|=>{
                        retgen2 = ret;
                        (*real_handler)(resolve);
                        return;
                    };
                    let clp = &ret_handler;
                    let arg2 = unsafe_cast<||=>void>(clp);
                    f(iter,arg2);

                    return;
                };
                let yr= (eff_ret,wrapped_f);
                yield return yr;
                break;
            }
            yr => {
                yield return yr;
            }
        }
    }



    // executeGenerator(fnUsingEff2());
    let a = retgen2;
    let re = 0;

    let f = |next:*||=>void,handler:*||=>void| => {
        let real_handler = unsafe_cast<||i64|=>void,i64|=>void>(handler);
        let resolve = |ret|=> {
            re = ret;
            (*next)();
            return;
        };
        (*real_handler)(resolve,a);

        return;
    };
    let yr= (effect1,f);
    yield return yr;
}


gen fn fnUsingEff2() Iterator<(i64, |*||=>void,*||=>void| =>void)> {
    // executeGenerator(fnUsingEff2());
    let a = 1;
    let re = 0;

    let f = |next:*||=>void,handler:*||=>void| => {
        let real_handler = unsafe_cast<||i64|=>void,i64|=>void>(handler);
        let resolve = |ret|=> {
            re = ret;
            (*next)();
            return;
        };
        (*real_handler)(resolve,a);

        return;
    };
    let yr= (effect1,f);
    yield return yr;

    f = |next:*||=>void,handler:*||=>void| => {
        let real_handler = unsafe_cast<|||=>void,i64|=>void>(handler);
        let resolve = ||=> {
            (*next)();
            return;
        };
        (*real_handler)(resolve,100);

        return;
    };
    // return
    yr= (eff_ret,f);
    yield return yr;
}


fn executeGenerator(g:Iterator<(i64, |*||=>void,*||=>void| =>void)>) void {
    let eff = g.next();
    if eff is None {
        return;
    }
    match eff as (i64, |*||=>void,*||=>void| =>void)! {
        (1,r) => {
            let cl =|resolve, a| => {
                eff1handler(resolve, a);
                return;
            };
            let next = & ||=>{
                executeGenerator(g);
                return;
            };
            let clp = &cl;
            let arg2 = unsafe_cast<||=>void>(clp);
            r(next,arg2);
        }
        (10086,r) => {
            let cl =|resolve:||=>void| => {
                resolve();
                return;
            };
            let next = & ||=>{
                executeGenerator(g);
                return;
            };
            let clp = &cl;
            let arg2 = unsafe_cast<||=>void>(clp);
            r(next,arg2);
        }
        (n, f) => {
            panic!("no handle", n);
        }
    }
    return;
}


fn eff1handler(resolve: |i64|=>void,a:i64) void {
    resolve(a+1);
    return;
}

The above code is runable in Pivot Lang. Reference