TopicsLanguage Designleft-side

Challenge: Add an “Array-like notation” to the Calculator

Objective

Add an array notation to the language with a syntax like the illustrated in the example below:

examples/arr.calc
a = [4+2;5+3i;9-i], # Creates a new function 0 => 4+2, 1 => 5+3i, 2 => 9-i 
print(a(0)), # { re: 6, im: 0 }
a(1) = 333,  
print(a(1)), # { re: 333, im: 0 }
print(a)     # { re: 6, im: 0 }, { re: 333, im: 0 }, { re: 9, im: -1 } ] or s.t. similar

Where the assignment a = [4+2;5+3i;9-i] creates a new function a that maps indexes to values: 0 => 4+2, 1 => 5+3i, 2 => 9-i.

The execution of the code above should print an output like the following:

âžś  left-side-solution git:(debug) âś— bin/calc2js.mjs examples/arr.calc | node
{ re: 6, im: 0 }
{ re: 333, im: 0 }
[ { re: 6, im: 0 }, { re: 333, im: 0 }, { re: 9, im: -1 } ]

What will you do when the index is out of bounds or not defined?

null

đź’ˇ

Add null to the language and use it as the default value for undefined indexes.

print(a(9)), # null

A Translation Scheme

Here is a possible translation scheme for the array notation:

examples/arr2.calc
âžś  left-side-solution git:(debug) âś— bin/calc2js.mjs examples/arr2.calc       
#!/usr/bin/env node 
const { arr, Complex, print, assign } = require("/Users/casianorodriguezleon/campus-virtual/2425/pl2425/practicas/left-side/left-side-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")))), // { re: 6, im: 0 }
assign($a, [Complex("1")], Complex("333"))), 
print($a(Complex("1")))), // { re: 333, im: 0 }
print($a(Complex("9")))), // null
print($a);                // [ { re: 6, im: 0 }, { re: 333, im: 0 }, { re: 9, im: -1 } ]

Tests

Add tests for the new functionality.

Challenge: Add an “Object-like notation” to the Calculator

Objective

Add an object notation to the language with a syntax like the illustrated in the example below:

examples/object.calc
a = { "a": 4+2; "b": 5+3i; "c": 9-i}, // Creates a new function "a" => 4+2, "b" => 5+3i, "c" => 9-i 
print(a("a")), // { re: 6, im: 0 }
a("b") = 333,  
print(a("b"))  // { re: 333, im: 0 }

Where the assignment a = { "a": 4+2; "b": 5+3i; "c": 9-i} creates a new function a that maps indexes to values: "a" => 4+2, "b" => 5+3i, "c" => 9-i and otherwise returns null.

The execution of the code above should print an output like the following:

âžś  left-side-solution git:(debug) âś— bin/calc2js.mjs examples/object.calc | node
{ re: 6, im: 0 }
{ re: 333, im: 0 }

null

đź’ˇ

What will you do when the “index” not defined?

Add null to the language and use it as the default value for undefined indexes.

print(a("d")), # null

A Translation Scheme

Here is a possible translation scheme for the object notation:

examples/object2.calc
âžś  left-side-solution git:(debug) âś— bin/calc2js.mjs examples/object2.calc      
#!/usr/bin/env node 
const { obj, Complex, print, assign } = require("/Users/casianorodriguezleon/campus-virtual/2425/pl2425/practicas/left-side/left-side-solution/src/support-lib.js"); 
/* End of support code */
let $a;
 
(((($a = obj({
    "a": Complex("4").add(Complex("2")),
    "b": Complex("5").add(Complex("3i")),
    "c": Complex("9").sub(Complex("i"))
}), 
print($a("a"))),          // { re: 6, im: 0 }
assign($a, ["b"], Complex("333"))), 
print($a("b"))),          // { re: 333, im: 0 }
print($a("d")))           // null

Introduce the grammar and lexical rules for the object notation. Write the obj function that creates an object-like notation for the calculator language.

Tests

Add tests for the new functionality.

Challenge: Add an “On predicate assignment” to the Calculator (too difficult)

This is a challenge that can be the subject for the TFA.

Objective

Add an on predicate assignment to the language with a syntax like the illustrated in the example below:

examples/on-predicate.calc
f = fun(x) { x + 1}, 
even = fun(n) { n%2 == 0 }, // even is a predicate describing the set of even numbers 
f(on even) = fun(x) { x*x }, // Syntax for modification in a whole set
print(f(2)),                 // 4 = 2*2  since 2 is even
print(f(1))                  // 2 = 1+1  since 1 is odd 

Where the assignment f(on even) = fun(x) { x*x } modifies the function f to return the square of the input z when the predicate even(z) is true, and z+1 otherwise.

The execution of the code above should print an output like the following:

âžś  left-side-solution git:(debug) âś— bin/calc2js.mjs examples/on-predicate.calc | node
{ re: 4, im: 0 }
{ re: 2, im: 0 }

Semantic when predicates Intersect

When two predicates intersect, the last assignment prevails:

even = fun(n) { n%2 == 0 }, // even is a predicate describing the set of even numbers 
multipleOf3 = fun(n) { n%3 == 0 }, // multipleOf3 is a predicate describing the set of numbers multiple of 3
f(on even) = 4,
f(on multipleOf3) = 9,
print(f(6)) // 9 The last assignment always prevails

Nested on predicates

When a function returns a function, the on predicate can be nested:

f = fun(x) { fun(y) { x + y } }, 
even = fun(n) { n%2 == 0 }, # The set of even numbers 
odd = fun(n) { n%2 != 0 }, # The set of odd numbers
f(on even)(on odd) = fun(x) { fun(y) { x*y } }, # Nested predicates: x in even && y in odd
print(f(4)(5)),        # 20  since 4 is even and 5 is odd
print(f(3)(5))         #  8  since 3 is not even
print(f(4)(6))         # 10  since 4 is even and 6 is not odd

Consequences

I believe this will be the behavior on an array:

even = fun(n) { n%2 == 0 }, # The set of even numbers 
odd = fun(n) { n%2 != 0 }, # The set of odd numbers
a = [8,7,6,5,4,3,2,1],
c = fun(x) { x + 3 },
b = a(on even),
b = c(on odd),
print(b(0)), // 8
print(b(1)), // 4 = 1+3
print(b(2)), // 6 

Tests

Add tests for the new functionality.

Augmenting indexation: Finite vs Infinite Data Structures

From the early days of language programming design, indexable data structures have been restricted to value-semantic (finite) structures like integers, strings, and booleans. This is why in most languages you have expressions like a[0] or a["key"] but there are no data structures that can be indexed with a function like a[x => x*x] or even on an array a[[3,2]] = 4 or a[{x :5}]. Even indexing in a float is risky because of the inherent nature of how floating-point arithmetic works.

One idea here is to extend the equality ”==” operator so that we can differentiate JSON-like arrays and objects and those are compared by value:

a = [4+2;5+3i;[9-i]], //JSONABLE function
b = [6;5+3i;[3*3-i]], // Another JSONABLE: a == b in "deep equality"
print(a == b), // true

If we implement assignable functions using a cache that computes the key/elements so that finite JSON-like objects that are equal are mapped to the same cache entry, then a program like the following:

a = [4+2;5+3i;[9-i]],
b = [6;5+3i; [9-i]], // a == b in "deep equality"
f = fun(x) { x + 1 },
f(a) = 4,
print(f(b)), // 4. Since the entry for "b" in the cache of "f" is the same than for "a"

will print 4 instead of function ... (since when using identity f(b) = [6;5+3i; [9-i]]+1 will be a function 0 => 7, 1 => 6+3i, 2 => [9-i]+1.

To differentiate between finite (JSONABLE) and infinite (non-JSONABLE) data structures, we need some way to mark those structures in the language.