Closed bobbicodes closed 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
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;
}
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:
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 -
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.