Currying in Egg
When the argument used to index a function object is not an attribute of the function
someFun[arg1, ... ] # and "arg1" is not a property of "someFun"
then we want arg1, ...
to be interpreted as arguments for someFun
and the expression returns the currying of the function (opens in a new tab) on arg1, ...
.
For instance:
✗ cat examples/curry-no-method.egg
print(+[4](2))
In this version of the Egg interpreter +
is a function that takes an arbritrary number of numbers:
and returns its sum. The curried
is the function defined by
Here is the implementation of the arithmetic operations in this version of the Egg interpreter that take an arbritrary number of numbers:
// arithmetics
[
'+',
'-',
'*',
'/',
'**',
].forEach(op => {
topEnv[op] = new Function('...s', `return s.reduce((a,b) => a ${op} b);`);
});
Execution:
➜ egg-oop-parser-solution git:(master) ✗ bin/eggc.js examples/curry-no-method.egg
➜ egg-oop-parser-solution git:(master) ✗ bin/evm examples/curry-no-method.json
6
However, if the attribute exists we want an ordinary property evaluation, as in this example:
➜ egg-oop-parser-solution git:(master) cat examples/function-length-property.egg
do(
def(f, fun(x, y, +(x,y))),
print(f["numParams"]) # JS length property is not supported
)
➜ egg-oop-parser-solution git:(master) ✗ bin/egg examples/function-length-property
2
Design Consideration
The decision of overloading the meaning of the property access for functions is a risky one but has few consequences over the grammar design.
The decision of overloading the meaning of the property access for functions has consequences during the interpretation phase.
In this case the idea behind the proposal is that
Any potential argument of a function can be viewed as a property of such function whose value is the function curried for that argument
which makes the design proposal consistent with the idea of property
Currying and the dot operator
The dot operator for objects a.b
is defined in such a way that a.b
and a["b"]
are the same thing. This is why the former program examples/curry-no-method.egg
can be rewritten this way:
➜ egg-oop-parser-solution git:(master) ✗ cat examples/curry-no-method-dot.egg
print(+.4(2))
➜ egg-oop-parser-solution git:(master) ✗ bin/egg examples/curry-no-method-dot
6
Changing the evaluate
method
You have to add the code in lines 12-14 to return the curryfied function:
evaluate(env) {
if (this.operator.type == "word" && this.operator.name in specialForms) {
// ... ?
}
let theObject = this.operator.evaluate(env);
let propsProcessed = this.args.map((arg) => arg.evaluate(env));
let propName = checkNegativeIndex(theObject, propsProcessed[0]);
if (theObject[propName] || propName in theObject) {
// ... theObject has a property with name "propName"
} else if (typeof theObject === "function") {
// theObject is a function, curry the function
// using propsProcessed as fixed arguments
} else
throw new TypeError(`...`);
}
Examples of currying 4["+", 5](3)
➜ eloquentjsegg git:(main) ✗ cat test/examples/curry-multiple-indices.egg
do(
print(4["+", 5](3)),
print(4["+", 5, 9](3))
)
Here is the execution:
➜ eloquentjsegg git:(main) ✗ bin/egg.js test/examples/curry-multiple-indices.egg
12
21
and here is the section of AST corresponding to the sub-expression 4["+", 5](3)
:
{
"type": "apply",
"operator": {
"type": "property",
"operator": { "type": "value", "value": 4 },
"args": [
{ "type": "value", "value": "+" },
{ "type": "value", "value": 5 }
]
},
"args": [
{ "type": "value", "value": 3 }
]
}