Challenges
TFA
Functions on the left side of an assignment

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:

test/data/input/fun.calc
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:

test/data/expectedjs/fun.js
➜  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:

test/data/input/fun-on-the-left-side.calc
➜  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

test/data/expectedjs/fun-on-the-left-side.js
➜  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:

test/data/input/fun-on-the-left-side-boolean.calc
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:

test/data/input/fun-on-the-left-side-string.calc
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:

test/data/input/fun-on-the-left-side-function.calc
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

  1. a function $f that is going to be modified,
  2. an array of arguments cacheArgs like [ Complex("2"), Complex("3")] that correspond to the source nested calls f(2)(3)
  3. the value to return Complex("8") in the example f(2)(3) = 8
  4. the index i to recursively traverse the cacheArgs array.
src/assign.js
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:

test/data/input/fun-manyargs.calc
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):

test/data/expectedjs/fun-manyargs.js
#!/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 translator

Clase del 13/03/2024. Building the Egg AST for 'ID apply'. Adding loops to the calculator language

Adding loops to the calculator language. Building a lexer generator

Pruebas, 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

One way to set GitHub as the registry is to add a publishConfig field to your package.json file

package.json
{
  "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

  1. My old notes in memoization (opens in a new tab) of the fibonacci function in Ruby and
  2. The wikipedia section on Memoization (opens in a new tab).
  3. Memoization of the require in Egg (opens in a new tab)
  4. memoize (opens in a new tab) npm package by Sindre Sorhus
  5. Memoization of Multi-Parametered Functions in JavaScript (opens in a new tab) by Joseph Sikorski. Jul 14, 2017
  6. Callable Objects in JavaScript (opens in a new tab) 2022
  7. Creating Callable Objects in JavaScript (opens in a new tab)
  8. Repo /ULL-ESIT-PL/callable-objects (opens in a new tab)