Functions on the left side of an assignment
Goal: Functions on the left side of an assignment
In the calculator language, the left side of an assignment only an ID is allowed. We want to extend the language to allow the modification of functions.
Introduction
We want to extend the language so that on the left side of an assignment you can have a function modification. For example, the following code should be valid:
f = fun(x) { x + 1 },
f(0+2) = 8,
f(1+3) = 1000,
write(f(0)), # 1
write(f(2)), # 8
write(f(4)) # 1000
The translation of the former code can be s.t. like the following:
➜ functions-solution git:(array-map) bin/calc2js.mjs test/data/input/fun.calc
#!/usr/bin/env node
const { Complex, assign, write } = require("/Users/casianorodriguezleon/campus-virtual/2223/pl2223/practicas/functions/functions-solution/src/support-lib.js");
/* End of support code */
let $f;
(((($f = function($x) {
return $x.add(Complex("1"));
},
$f = assign($f, [Complex("0").add(Complex("2"))], Complex("8"), 0)),
$f = assign($f, [Complex("1").add(Complex("3"))], Complex("1000"), 0)),
write($f(Complex("0")))),
write($f(Complex("2")))),
write($f(Complex("4")));
When you run the former code you get the following output:
➜ functions-solution git:(array-map) bin/calc2js.mjs test/data/input/fun.calc | node
1
8
1000
Modifying a function with nested calls on the left side
let's consider the following example in which a nested call occurs in the left hand side of the assignment:
➜ functions-solution git:(left-side) cat test/data/input/fun-on-the-left-side.calc
f = fun(x) { fun(y) { x + y } },
f(2)(3) = 8,
write(f(2)(3)), # 8
write(f(2)(5)), # 7
write(f(3)(1)), # 4
write(f(9)(2)) # 11
The former code modifies the code of the function $f
created in the first assignment
➜ functions-solution git:(array-map) bin/calc2js.mjs test/data/input/fun-on-the-left-side.calc
#!/usr/bin/env node
const { assign, Complex, write } = require("/Users/casianorodriguezleon/campus-virtual/2223/pl2223/practicas/functions/functions-solution/src/support-lib.js");
/* End of support code */
let $f;
(((($f = function($x) {
return function($y) {
return $x.add($y);
};
},
$f = assign($f, [Complex("2"), Complex("3")], Complex("8"), 0)),
write($f(Complex("2"))(Complex("3")))),
write($f(Complex("2"))(Complex("5")))),
write($f(Complex("3"))(Complex("1")))),
write($f(Complex("9"))(Complex("2")));
Nested calls on the left side and a function on the right side
In this example the assignment on line 6 modifies the function f
so that f(1)
returns a function that squares its argument:
f = fun(x) {
fun(y) {
fun (z) { x + y + z }
}
},
f(1) = fun(y) { y*y },
print(f(1)(3)), # 9
print(f(2)(3)(5)) # 10
Is for that reason that f(1)(3)
is the square of 3
and f(2)(3)(5)
is 2 + 3 + 5
that is 10
.
Empty arguments on the left side
We are going to consider that when a call to a function occurs on a left side of an assignment expression it has to have argument.
f = fun() { 1 },
f() = 8 # Error message
➜ functions-solution git:(array-map) bin/calc2js.mjs test/data/input/fun-empty-on-the-left-side.calc
Error: Can't assign to "f" with no arguments
Types and symmetry
The new feature should work with all the types supported by the calculator language. Here is an example for booleans:
f = fun(x) { fun(y) { x + y } },
f(true)(3) = 8, #
write(f(true)(3)), # 8 true has an equals method!
write(f(true;4)), # 5
write(f(4)(true)) # 5
The notation f(true;4)
is equivalent to f(true)(4)
and will be introduced in the next section.
For strings:
f = fun(x) { fun(y) { x + y } },
f("string")(3) = 8,
f(2)("string") = 9,
write(f("string")(3)), # 8
write(f("string")(4)), # "string4"
write(f(2)("string")) # 9
When the argument/index is a function:
f = fun(x) { fun(y) { x + y } },
g = fun(y) { y*y },
f(g) = fun(v) { f(g(v))(3) },
write(f(2)(3)), # 5
write(f(g)(4)) # 19
The assign function
As we have seen the assign
function receives
- a function
$f
that is going to be modified, - an array of arguments
cacheArgs
like[ Complex("2"), Complex("3")]
that correspond to the source nested callsf(2)(3)
- the value to return
Complex("8")
in the examplef(2)(3) = 8
- the index
i
to recursively traverse thecacheArgs
array.
function assign(f, cacheArgs, cacheValue, i) {
let next = i+1;
let cacheArgument = cacheArgs[i];
return function (...args) {
let auxF = f;
if (cacheArgument == null) {
let error = new Error('Invalid null argument on left side of assignment');
throw error;
}
if (args[0].equals && args[0]?.equals(cacheArgument) || args[0] == cacheArgument) {
if (cacheArgs.length === next) {
return cacheValue;
}
auxF = f(...args);
return assign(auxF, cacheArgs, cacheValue, next)
}
return auxF(...args);
}
}
Currying and multiple arguments for functions
We are going to introduce a notation of lists of identifiers separated by semicolons ;
inside the parameter section of a function declaration.
Correspondingly we also introduce list of semicolons separated expressions inside a function call. The following example illustrates its use:
f = fun(x;y) {
x+y
},
print(f(2;3)) # same as f(2)(3)
The translation shows how the function f
is curryfied and converted onto a function of one argument that returns a
function. In the same way f(2;3)
is translated as f(2)(3)
:
#!/usr/bin/env node
const { print, Complex } = require("/Users/casianorodriguezleon/campus-virtual/2223/pl2223/practicas/functions/functions-solution/src/support-lib.js");
/* End of support code */
let $f;
$f = function($y) {
return function($x) {
return $x.add($y);
};
}, print($f(Complex("2"))(Complex("3")));
When you run the former code you get the following output:
➜ functions-solution git:(array-map) ✗ node test/data/expectedjs/fun-manyargs.js
{ re: 5, im: 0 }
Goal: Array notation for functions and otherwise
The notation
a = [4;7;9 ... 0]
defines a
as a function such that a(0)
returns 4
, a(1)
returns 7
, and a(2)
returns 9
and otherwise
returns 0
.
If the ... expression
is not present, the function returns an special object called
null
for any argument not explicitly defined.
a = [4+2;5+3i;9-i],
print(a(0)), # { re: 6, im: 0 }
a(1) = 333,
print(a(1)), # { re: 333, im: 0 }
print(a(9)), # null
print(a) # { re: 6, im: 0 }, { re: 333, im: 0 }, { re: 9, im: -1 } ]
The former code translates to
➜ functions-solution git:(array-map) ✗ bin/calc2js.mjs test/data/input/arr.calc | npx prettier --parser babel
#!/usr/bin/env node
const {
arr,
Complex,
print,
memoize
} = require('/Users/casianorodriguezleon/campus-virtual/2223/pl2223/practicas/functions/functions-solution/src/support-lib.js');
/* End of support code */
let $a;
((((($a = arr(
Complex('4').add(Complex('2')),
Complex('5').add(Complex('3i')),
Complex('9').sub(Complex('i'))
)),
print($a(Complex('0')))),
($a = memoize($a, [Complex('1')], Complex('333'), 0))),
print($a(Complex('1')))),
print($a(Complex('9')))),
print($a);
Hashes/Maps/Objects
Proposed syntax for hashes/maps:
a = { 4: 5; 7: 3i; "hello": 6 ... 0}
meaning a function that a(4) = 5
, a(7) = 3i
, a("hello") = 6
and otherwise a(x) = 0
for all x
.
An alternative to explore/study is to consider the introduction of the meth
keyword that will define a functions that usea the "object" as a context. For example, the following code:
b = { "x": 5; "add": meth(z) { x + z} },
f("add")(3) # 8
null object
Introduce null
as an extension of 0
and the empty string ""
so that it holds the following properties:
null + 4 == 4,
null + "hello" == "hello", # this is not the JS behavior
null * 4 == 0,
4 - null == 4,
fun(x) { x + 1 } * null # is the same as fun(x) { 0 },
null && true == false,
Notice that if a
is a function like in the former example a * null
is the function a(x) = 0
for any x
.
Videos
2024/03/20
Clase del 20/03/2024. Your workflow when adding a new feature to a translatorPruebas, Covering e Integración Continua
Escriba las pruebas, haga un estudio de cubrimiento usando c8 (opens in a new tab) y añada integración continua usando GitHub Actions.
Lea las secciones Testing with Mocha y Jest.
Publishing a package to the GitHub Registry
See the chapter Publishing a package to the GitHub Registry and the sections
- Configure npm
- What are scopes?
- What is Github Registry?
- Other ways to set the Scope (opens in a new tab)
One way to set GitHub as the registry is to add a publishConfig
field to your package.json
file
{
"name": "@ull-esit-pl-2324/functions-name-ape1-ape2-aluXXX",
"version": "1.2.0",
"description": "A lab for PL. Adding Functions to our Calculator",
"main": "src/transpile.js",
"bin": {
"calc2js": "./bin/calc2js.js"
},
"publishConfig": {
"registry": "https://npm.pkg.github.com"
},
...
}
I noticed many of you are having a hard time trying to publish the module to the GitHub Package Registry.
This class 2023/03/14 from 18:03 to to 35:00 may help to overcome the gotchas when publishing a private npm module in the github package registry.
- Start to talk about publishing a module at minute 18:03.
- How to get the GitHub token at minute 19:58
- Providing the token to the npm client at minute 23:00
- Issuing
npm publish
at minute 23:52 - Associating the GitHub Organization ULL-ESIT-PL-2324 as a scope to the github registry at minute 28:13
- From minute 34:25 and on, we discuss how to build a web site using a static generator like Vuepress and how to deploy it to GitHub Pages.
Documentación
Documente
el módulo incorporando un README.md
y la documentación de la función exportada usando JsDoc.
Lea la sección Documenting the JavaScript Sources
Rubric
left-side Repos
References
- My old notes in memoization (opens in a new tab) of the
fib
onacci function in Ruby and - The wikipedia section on Memoization (opens in a new tab).
- Memoization of the require in Egg (opens in a new tab)
- memoize (opens in a new tab) npm package by Sindre Sorhus
- Memoization of Multi-Parametered Functions in JavaScript (opens in a new tab) by Joseph Sikorski. Jul 14, 2017
- Callable Objects in JavaScript (opens in a new tab) 2022
- Creating Callable Objects in JavaScript (opens in a new tab)
- Repo /ULL-ESIT-PL/callable-objects (opens in a new tab)