bobbicodes / bobbi-lisp

Interactive Lisp environment for learning Clojure
https://bobbicodes.github.io/bobbi-lisp/
0 stars 0 forks source link

variadic arity bodies #10

Closed bobbicodes closed 12 months ago

bobbicodes commented 12 months ago

I recently implemented the multiarity functionality in what I believe to be the "proper" way, at the function level. My previous attempt was a hack on top of the defn special form, which is now the defn macro. But I forgot to handle arity bodies with rest (&) parameters.

Actually I just realized that it's more wrong than I thought. It assumes that the arity bodies are in order! Meaning, defined in order from least to greatest number of args. Ouch... although it is idiomatic for them to be, so if I hadn't thought of this I may not have had problems for awhile.

I suppose I could sort them so they are actually in order, but I didn't even follow my design sketch, which was this:

export function _multiarity_fn(Eval, Env, bodies, env) {
    var fn = function () {
        for (let i = 0; i < bodies.length; i++) {
            if (arguments.length === bodies[i][0].length) {
                return Eval(ast, new Env(env, bodies[i][0], arguments));
            }
        }
        throw new Error("No arity defined for " + arguments.length + " args");
    }
}

That's the basic idea, except the logic for calling the correct arity ended up going in the interpreter, not the type. It seems straightforward enough to put the for loop in there, with the additional step of checking for an arglist with an &. I believe this needs to be done after we have first checked if there is a matching fixed arity.

Actually it's a bit more complicated -

(defn myfn
  ([a b c] (str a b c))
  ([a & b] (str "variadic" a (apply str b))))

(myfn "hello" "kitty")
"variadichellokitty"

We need to make sure we don't make the mistake of picking the one with the rest parameter if it happens to match the argument count. Also, trying to have more than one signature with a rest parameter should return an error rather than picking one, if I want to get really fancy.

bobbicodes commented 12 months ago

Here are the relevant parts of the implementation for convenience. First, the multifn type:

export function multifn(Eval, Env, bodies, env) {
    var fn = function () {
        return Eval(bodies[arguments.length][1], 
            new Env(env, bodies[arguments.length][0], arguments));
    }
    fn.__meta__ = null;
    fn.__multifn__ = true
    fn.__ast__ = bodies;
    fn.__gen_env__ = function (args) {
        return new Env(env, bodies[args.length][0], args)
    }
    fn._ismacro_ = false;
    return fn;
}

Then, the step in the interpreter that handles it:

if (f.__multifn__) {
    var arity = el.slice(1).length
    ast = f.__ast__[arity][1]
    env = f.__gen_env__(el.slice(1));
}

Yes, it's excessively simple to the point of wrong. It could almost not be more wrong unless it was written by Wrong E.W. Wrongenstein

bobbicodes commented 12 months ago

It's done! It actually works!

function findVariadic(bodies) {
    for (let i = 0; i < bodies.length; i++) {
        // I don't know how to recognize symbols by value,
        // so it's either this or loop through them all
        if (bodies[i][0].toString().includes('&')) {
            return bodies[i]
        }
    }
}

function findFixedArity(arity, bodies) {
    for (let i = 0; i < bodies.length; i++) {
        if (bodies[i][0].length === arity && !bodies[i][0].toString().includes('&')) {
            return bodies[i]
        }
    }
}

export function multifn(Eval, Env, bodies, env) {
    var fn = function () {
        var arity = arguments.length
        var body = findFixedArity(arity, bodies) || findVariadic(bodies)
        return Eval(body[1], 
            new Env(env, body[0], arguments));
    }
    fn.__meta__ = null;
    fn.__multifn__ = true
    fn.__ast__ = function(args) {
        var arity = args.length
        var ast = findFixedArity(arity, bodies) || findVariadic(bodies)
        return ast[1]
    }
    fn.__gen_env__ = function (args) {
        var arity = args.length
        var body = findFixedArity(arity, bodies) || findVariadic(bodies)
        return new Env(env, body[0], args)
    }
    fn._ismacro_ = false;
    return fn;
}