From ff68d7ac185c12246557d61c4d4f8bc90640784b Mon Sep 17 00:00:00 2001 From: Tiago Katcipis Date: Sat, 5 Aug 2017 14:03:42 -0300 Subject: [PATCH 01/20] Add proposal for scope management --- proposal/1-scope-management.md | 71 ++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 proposal/1-scope-management.md diff --git a/proposal/1-scope-management.md b/proposal/1-scope-management.md new file mode 100644 index 00000000..ddaf3886 --- /dev/null +++ b/proposal/1-scope-management.md @@ -0,0 +1,71 @@ +# Proposal: Proper scope management + +## Abstract + +Currently on nash there is no way to properly work +with closures because scope management is very limited. + +Lets elaborate on the problem by implementing a +list object by instantiating a set of functions +that manipulates the same data. + +```sh +fn list() { + + l = () + + fn add(val) { + l <= append($l, $val) + } + + fn get(i) { + return $l[$i] + } + + fn string() { + print("list: [%s]\n", $l) + } + + return $add, $get, $string +} +``` + +The idea is to hide all list data behind these 3 functions +that will manipulate the same data. The problem is that today +this is not possible, using this code: + +```sh +add, get, string <= list() + +$add("1") +$add("2") +$string() + +v <= $get("0") +echo $v +``` + +Will result in: + +``` +list: [] +/tmp/test.sh:27:5: /tmp/test.sh:11:23: Index out of bounds. len($l) == 0, but given 0 +``` + +As you can see, even when we call the **add** function the list +remains empty, why is that ? The problem is on the add function: + +```sh +fn add(val) { + l <= append($l, $val) +} +``` + +When we reference the **l** variable it uses the reference on the +outer scope (the empty list), but there is no way to express syntactically +that we want to change the list on the outer scope instead of creating +a new variable **l**. That is why the **get** and **print** functions +are always referencing an outer list **l** that is empty, a new one +is created each time the add function is called. + +In this document we brainstorm about possible solutions to this. From 453247d40c1aef8c2c8481cd6a0a4f6a35fb5ad2 Mon Sep 17 00:00:00 2001 From: Paulo Pizarro Date: Sat, 5 Aug 2017 15:30:08 -0300 Subject: [PATCH 02/20] Added 'var' and 'outer' proposals --- proposal/1-scope-management.md | 73 ++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/proposal/1-scope-management.md b/proposal/1-scope-management.md index ddaf3886..90098ce2 100644 --- a/proposal/1-scope-management.md +++ b/proposal/1-scope-management.md @@ -69,3 +69,76 @@ are always referencing an outer list **l** that is empty, a new one is created each time the add function is called. In this document we brainstorm about possible solutions to this. + +## Proposal I - "var" + +```sh +fn list() { + + // initialize an "l" variable in this scope + var l = () + + fn add(val) { + + // use the "l" variable from parent scope + // find first in the this scope if not found + // then find variable in the parent scope + + l <= append($l, $val) + } + + fn get(i) { + // use the "l" variable from parent scope + return $l[$i] + } + + fn string() { + // use the "l" variable from parent scope + print("list: [%s]\n", $l) + } + + fn not_clear() { + // force initialize a new "l" variable in this scope + // because this the "l" list in the parent scope is not cleared + var l = () + } + + return $add, $get, $string +} +``` + +## Proposal II - "outer" + +```sh +fn list() { + + // initialize an "l" variable in this scope + l = () + + fn add(val) { + // use the "l" variable from the parent + outer l <= append($l, $val) + } + + fn get(i) { + // use the "l" variable from the parent + outer l + return $l[$i] + } + + fn string() { + // use the "l" variable from the parent + outer l + print("list: [%s]\n", $l) + } + + fn not_clear() { + // how we not inform to use outer scope + // the "l" list in the parent scope is not cleared + l = () + } + + return $add, $get, $string +} + +``` From ccd0ba5c24c87457d470c691aa597038357af8ea Mon Sep 17 00:00:00 2001 From: Tiago Natel de Moura Date: Sat, 5 Aug 2017 19:01:32 -0300 Subject: [PATCH 03/20] fix comments Signed-off-by: Tiago Natel de Moura --- proposal/1-scope-management.md | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/proposal/1-scope-management.md b/proposal/1-scope-management.md index 90098ce2..cba37099 100644 --- a/proposal/1-scope-management.md +++ b/proposal/1-scope-management.md @@ -49,7 +49,7 @@ Will result in: ``` list: [] -/tmp/test.sh:27:5: /tmp/test.sh:11:23: Index out of bounds. len($l) == 0, but given 0 +/tmp/test.sh:27:5: /tmp/test.sh:11:23: Index out of bounds. len($l) == 0, but given 0 ``` As you can see, even when we call the **add** function the list @@ -83,7 +83,6 @@ fn list() { // use the "l" variable from parent scope // find first in the this scope if not found // then find variable in the parent scope - l <= append($l, $val) } @@ -121,14 +120,12 @@ fn list() { } fn get(i) { - // use the "l" variable from the parent - outer l + // use the "l" variable from the parent outer l return $l[$i] } fn string() { - // use the "l" variable from the parent - outer l + // use the "l" variable from the parent outer l print("list: [%s]\n", $l) } From 5bc7ee75aa020704eeb26fe3c8ec0ddee82edc35 Mon Sep 17 00:00:00 2001 From: Tiago Natel de Moura Date: Sat, 5 Aug 2017 20:45:12 -0300 Subject: [PATCH 04/20] elaborate the proposal I Signed-off-by: Tiago Natel de Moura --- proposal/1-scope-management.md | 69 +++++++++++++++++++++++++++++++++- 1 file changed, 68 insertions(+), 1 deletion(-) diff --git a/proposal/1-scope-management.md b/proposal/1-scope-management.md index cba37099..22e934a9 100644 --- a/proposal/1-scope-management.md +++ b/proposal/1-scope-management.md @@ -72,6 +72,22 @@ In this document we brainstorm about possible solutions to this. ## Proposal I - "var" +This proposal adds a new keyword `var` that will be used to declare and +initialize variables in the local scope. Is an error to use `var` with +an existent local variable (redeclare is forbiden). + +```js +var i = "0" +``` + +Normal assignments will only update existent variables. The assignment +must first look for the target variable in the local scope and then in +the parent, recursively, until it's found and then updated, otherwise +(in case the variable is not found) the interpreter must abort with +error. + +Below is how this proposal solves the scope management problem: + ```sh fn list() { @@ -79,7 +95,6 @@ fn list() { var l = () fn add(val) { - // use the "l" variable from parent scope // find first in the this scope if not found // then find variable in the parent scope @@ -106,6 +121,58 @@ fn list() { } ``` +Sintactically, the `var` statement is an extension of the assignment +and exec-assignment statements, and then it should support multiple +declarations in a single statement also. Eg.: + +```js +var i, j = "0", "1" + +var body, err <= curl -f $url + +var name, surname, err <= getAuthor() +``` + +One of the downsides of `var` is the requirement that none of the +targeted variable exists, because it makes awkward when existent +variables must be used in conjunction with new ones. An example is the +variables `$status` and `$err` that are often used to get process exit +status and errors from functions, respectively. + +The PR #227 implements this proposal but deviates in multiple +assignments to handle the downside above. The `var` statement was +implemented with the rules below: + +1. At least one of the targeted variables must do not exists; +2. The existent variables are just updated in the scope it resides; + +Below are some valid examples with #227: + +```js +var a, b = "0", "1" # works fine, variables didn't existed before + +var a, b = "2", "3" # error by rule 1 + +# works! c is declared but 'a' and 'b' are updated (by rule 2) +var a, b, c = "4", "5", "6" + +# works, variables first declared +var users, err <= cat /etc/passwd | awk -F ":" "{print $1}" + +# also works, but $err just updated +var pass, err <= cat /etc/shadow | awk -F ":" "{print $2}" +``` + +The implementation above is handy but makes the meaning of `var` +confuse because it declares new variables **and** update existent ones +(in outer scopes also). Then making hard to know what variables are +being declared local and what are being updated, by just looking at +the statement, because the meaning will depend in the current +environment of variables. + +Another downside of `var` is their very incompatible nature. Every +nash script ever created will be affected. + ## Proposal II - "outer" ```sh From a0a2ea4a50a91f877c2f4d9310f36f820aeb4c4c Mon Sep 17 00:00:00 2001 From: Tiago Natel de Moura Date: Sat, 5 Aug 2017 20:54:59 -0300 Subject: [PATCH 05/20] fix comments and spaces Signed-off-by: Tiago Natel de Moura --- proposal/1-scope-management.md | 92 +++++++++++++++++++--------------- 1 file changed, 51 insertions(+), 41 deletions(-) diff --git a/proposal/1-scope-management.md b/proposal/1-scope-management.md index 22e934a9..9cca459d 100644 --- a/proposal/1-scope-management.md +++ b/proposal/1-scope-management.md @@ -11,22 +11,21 @@ that manipulates the same data. ```sh fn list() { + l = () - l = () + fn add(val) { + l <= append($l, $val) + } - fn add(val) { - l <= append($l, $val) - } + fn get(i) { + return $l[$i] + } - fn get(i) { - return $l[$i] - } + fn string() { + print("list: [%s]\n", $l) + } - fn string() { - print("list: [%s]\n", $l) - } - - return $add, $get, $string + return $add, $get, $string } ``` @@ -57,7 +56,7 @@ remains empty, why is that ? The problem is on the add function: ```sh fn add(val) { - l <= append($l, $val) + l <= append($l, $val) } ``` @@ -86,38 +85,49 @@ the parent, recursively, until it's found and then updated, otherwise (in case the variable is not found) the interpreter must abort with error. -Below is how this proposal solves the scope management problem: - -```sh -fn list() { - - // initialize an "l" variable in this scope - var l = () - - fn add(val) { - // use the "l" variable from parent scope - // find first in the this scope if not found - // then find variable in the parent scope - l <= append($l, $val) - } +```js +var count = "0" # declare local variable - fn get(i) { - // use the "l" variable from parent scope - return $l[$i] - } +fn inc() { + # update outer variable + count, _ <= expr $count "+" 1 +} - fn string() { - // use the "l" variable from parent scope - print("list: [%s]\n", $l) - } +inc() +print($count) # outputs: 2 +``` - fn not_clear() { - // force initialize a new "l" variable in this scope - // because this the "l" list in the parent scope is not cleared - var l = () - } +Below is how this proposal solves the scope management problem example: - return $add, $get, $string +```sh +fn list() { + # initialize an "l" variable in this scope + var l = () + + fn add(val) { + # use the "l" variable from parent scope + # find first in the this scope if not found + # then find variable in the parent scope + l <= append($l, $val) + } + + fn get(i) { + # use the "l" variable from parent scope + return $l[$i] + } + + fn string() { + # use the "l" variable from parent scope + print("list: [%s]\n", $l) + } + + fn not_clear() { + # force initialize a new "l" variable in this scope + # because this the "l" list in the parent scope is not cleared + var l = () + } + + return $add, $get, $string } ``` From dc8d348dcd7301e7d55b0c17587d7d2ad4e25297 Mon Sep 17 00:00:00 2001 From: Tiago Natel de Moura Date: Sat, 5 Aug 2017 20:56:41 -0300 Subject: [PATCH 06/20] fix comments and spaces Signed-off-by: Tiago Natel de Moura --- proposal/1-scope-management.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proposal/1-scope-management.md b/proposal/1-scope-management.md index 9cca459d..6c42f616 100644 --- a/proposal/1-scope-management.md +++ b/proposal/1-scope-management.md @@ -113,7 +113,7 @@ fn list() { fn get(i) { # use the "l" variable from parent scope - return $l[$i] + return $l[$i] } fn string() { From 0d5dce7c3ad33f8e0eecb89e09c6545a410f8162 Mon Sep 17 00:00:00 2001 From: Tiago Natel de Moura Date: Sat, 5 Aug 2017 20:58:30 -0300 Subject: [PATCH 07/20] add link to #227 Signed-off-by: Tiago Natel de Moura --- proposal/1-scope-management.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/proposal/1-scope-management.md b/proposal/1-scope-management.md index 6c42f616..a8365d79 100644 --- a/proposal/1-scope-management.md +++ b/proposal/1-scope-management.md @@ -149,14 +149,15 @@ variables must be used in conjunction with new ones. An example is the variables `$status` and `$err` that are often used to get process exit status and errors from functions, respectively. -The PR #227 implements this proposal but deviates in multiple -assignments to handle the downside above. The `var` statement was -implemented with the rules below: +The [PR #227](https://github.com/NeowayLabs/nash/pull/227) implements +this proposal but deviates in multiple assignments to handle the +downside above. The `var` statement was implemented with the rules +below: 1. At least one of the targeted variables must do not exists; 2. The existent variables are just updated in the scope it resides; -Below are some valid examples with #227: +Below are some valid examples with [#227](https://github.com/NeowayLabs/nash/pull/227): ```js var a, b = "0", "1" # works fine, variables didn't existed before From ccf7a2a38d54998340da8839ade85e9d8a2c893b Mon Sep 17 00:00:00 2001 From: Tiago Natel de Moura Date: Sat, 5 Aug 2017 21:00:41 -0300 Subject: [PATCH 08/20] change code syntax highlight Signed-off-by: Tiago Natel de Moura --- proposal/1-scope-management.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/proposal/1-scope-management.md b/proposal/1-scope-management.md index a8365d79..1d904e0a 100644 --- a/proposal/1-scope-management.md +++ b/proposal/1-scope-management.md @@ -85,7 +85,7 @@ the parent, recursively, until it's found and then updated, otherwise (in case the variable is not found) the interpreter must abort with error. -```js +```sh var count = "0" # declare local variable fn inc() { @@ -135,7 +135,7 @@ Sintactically, the `var` statement is an extension of the assignment and exec-assignment statements, and then it should support multiple declarations in a single statement also. Eg.: -```js +```sh var i, j = "0", "1" var body, err <= curl -f $url @@ -159,7 +159,7 @@ below: Below are some valid examples with [#227](https://github.com/NeowayLabs/nash/pull/227): -```js +```sh var a, b = "0", "1" # works fine, variables didn't existed before var a, b = "2", "3" # error by rule 1 From b43f98eda57b7c1197ea091e111bf5230b6649e1 Mon Sep 17 00:00:00 2001 From: Tiago Natel de Moura Date: Sat, 5 Aug 2017 21:10:39 -0300 Subject: [PATCH 09/20] elaborate the outer proposal text Signed-off-by: Tiago Natel de Moura --- proposal/1-scope-management.md | 49 +++++++++++++++++++--------------- 1 file changed, 27 insertions(+), 22 deletions(-) diff --git a/proposal/1-scope-management.md b/proposal/1-scope-management.md index 1d904e0a..d36de2b0 100644 --- a/proposal/1-scope-management.md +++ b/proposal/1-scope-management.md @@ -186,34 +186,39 @@ nash script ever created will be affected. ## Proposal II - "outer" +This proposal adds a new `outer` keyword that permits the update of +variables in the outer scope. Outer assignments with non-existent +variables is an error. + ```sh fn list() { + # initialize an "l" variable in this scope + l = () - // initialize an "l" variable in this scope - l = () - - fn add(val) { - // use the "l" variable from the parent - outer l <= append($l, $val) - } + fn add(val) { + # use the "l" variable from the parent + outer l <= append($l, $val) + } - fn get(i) { - // use the "l" variable from the parent outer l - return $l[$i] - } + fn get(i) { + # use the "l" variable from the parent outer l + return $l[$i] + } - fn string() { - // use the "l" variable from the parent outer l - print("list: [%s]\n", $l) - } + fn string() { + # use the "l" variable from the parent outer l + print("list: [%s]\n", $l) + } - fn not_clear() { - // how we not inform to use outer scope - // the "l" list in the parent scope is not cleared - l = () - } + fn not_clear() { + # "l" is not cleared, but a new a new variable is created (shadowing) + # because "outer" isn't specified. + l = () + } - return $add, $get, $string + return $add, $get, $string } - ``` + +The `outer` keyword has the same meaning that Python's `global` +keyword. From c1f059f21a81f3643f71789bc9cec8b13648096e Mon Sep 17 00:00:00 2001 From: Paulo Pizarro Date: Sun, 6 Aug 2017 10:08:27 -0300 Subject: [PATCH 10/20] Added one more example to 'var' proposal --- proposal/1-scope-management.md | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/proposal/1-scope-management.md b/proposal/1-scope-management.md index d36de2b0..969fc2c9 100644 --- a/proposal/1-scope-management.md +++ b/proposal/1-scope-management.md @@ -94,7 +94,7 @@ fn inc() { } inc() -print($count) # outputs: 2 +print($count) # outputs: 1 ``` Below is how this proposal solves the scope management problem example: @@ -184,6 +184,21 @@ environment of variables. Another downside of `var` is their very incompatible nature. Every nash script ever created will be affected. +Another behavior we need to discuss is whether all variables declared +within a function (statement) will be created at the beginning +(as scope is created) or only at the time a 'var' keywork is fond. + +```sh +var l = () + +func test() { + l <= append($l, "1") + var l = () +} + +print($l) # outputs: "1" +``` + ## Proposal II - "outer" This proposal adds a new `outer` keyword that permits the update of From 0bbc9e2bb8a92559c38f969af475d13ebdbb9387 Mon Sep 17 00:00:00 2001 From: Paulo Pizarro Date: Sun, 6 Aug 2017 12:18:21 -0300 Subject: [PATCH 11/20] Fixed typo --- proposal/1-scope-management.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proposal/1-scope-management.md b/proposal/1-scope-management.md index 969fc2c9..f7fd90a4 100644 --- a/proposal/1-scope-management.md +++ b/proposal/1-scope-management.md @@ -186,7 +186,7 @@ nash script ever created will be affected. Another behavior we need to discuss is whether all variables declared within a function (statement) will be created at the beginning -(as scope is created) or only at the time a 'var' keywork is fond. +(as scope is created) or only at the time a 'var' keywork is found. ```sh var l = () From 1ae9b78ac71423fb2d2c9584000fbb887cd06814 Mon Sep 17 00:00:00 2001 From: Paulo Pizarro Date: Mon, 7 Aug 2017 13:39:34 -0300 Subject: [PATCH 12/20] Fixed typo --- proposal/1-scope-management.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proposal/1-scope-management.md b/proposal/1-scope-management.md index f7fd90a4..0f8d61cc 100644 --- a/proposal/1-scope-management.md +++ b/proposal/1-scope-management.md @@ -191,7 +191,7 @@ within a function (statement) will be created at the beginning ```sh var l = () -func test() { +fn test() { l <= append($l, "1") var l = () } From 2d40d4ce8379ba80fa930f8916bb32a78342ad5d Mon Sep 17 00:00:00 2001 From: Tiago Katcipis Date: Wed, 9 Aug 2017 17:14:08 -0300 Subject: [PATCH 13/20] Small fixes on proposal --- proposal/1-scope-management.md | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/proposal/1-scope-management.md b/proposal/1-scope-management.md index 0f8d61cc..309a4c0c 100644 --- a/proposal/1-scope-management.md +++ b/proposal/1-scope-management.md @@ -69,21 +69,30 @@ is created each time the add function is called. In this document we brainstorm about possible solutions to this. -## Proposal I - "var" +## Proposal I - Create new variables explicitly -This proposal adds a new keyword `var` that will be used to declare and -initialize variables in the local scope. Is an error to use `var` with -an existent local variable (redeclare is forbiden). +On this proposal new variable creation requires an explicit +syntax construction. + +We could add a new keyword `var` that will be used to declare and +initialize variables in the local scope, like this: ```js var i = "0" ``` -Normal assignments will only update existent variables. The assignment -must first look for the target variable in the local scope and then in -the parent, recursively, until it's found and then updated, otherwise -(in case the variable is not found) the interpreter must abort with -error. +While the current syntax: + +```js +i = "0" +``` + +Will be assigning a new value to an already existent variable **i**. + +The assignment must first look for the target variable in the local +scope and then in the parent, recursively, until it's found and then updated, +otherwise (in case the variable is not found) the interpreter must abort +with error. ```sh var count = "0" # declare local variable @@ -97,7 +106,8 @@ inc() print($count) # outputs: 1 ``` -Below is how this proposal solves the scope management problem example: +Below is how this proposal solves the +scope management problem example: ```sh fn list() { @@ -131,7 +141,7 @@ fn list() { } ``` -Sintactically, the `var` statement is an extension of the assignment +Syntactically, the `var` statement is an extension of the assignment and exec-assignment statements, and then it should support multiple declarations in a single statement also. Eg.: From 49642139a33ce95e96e911ab1b59d791627bfc4b Mon Sep 17 00:00:00 2001 From: Tiago Katcipis Date: Mon, 18 Sep 2017 16:02:28 -0300 Subject: [PATCH 14/20] Consolidate the proposal 1 --- proposal/1-scope-management.md | 129 +++++++++++++++++++++------------ 1 file changed, 83 insertions(+), 46 deletions(-) diff --git a/proposal/1-scope-management.md b/proposal/1-scope-management.md index 309a4c0c..14604fc2 100644 --- a/proposal/1-scope-management.md +++ b/proposal/1-scope-management.md @@ -63,11 +63,13 @@ fn add(val) { When we reference the **l** variable it uses the reference on the outer scope (the empty list), but there is no way to express syntactically that we want to change the list on the outer scope instead of creating -a new variable **l**. That is why the **get** and **print** functions +a new variable **l** (shadowing the outer **l**). + +That is why the **get** and **print** functions are always referencing an outer list **l** that is empty, a new one is created each time the add function is called. -In this document we brainstorm about possible solutions to this. +In this document we navigate the solution space for this problem. ## Proposal I - Create new variables explicitly @@ -88,11 +90,10 @@ i = "0" ``` Will be assigning a new value to an already existent variable **i**. - -The assignment must first look for the target variable in the local -scope and then in the parent, recursively, until it's found and then updated, -otherwise (in case the variable is not found) the interpreter must abort -with error. +The assignment will first look for the target variable in the local +scope and then in the parent, traversing the entire stack, until it's +found and then updated, otherwise (in case the variable is not found) +the interpreter must abort with error. ```sh var count = "0" # declare local variable @@ -106,8 +107,7 @@ inc() print($count) # outputs: 1 ``` -Below is how this proposal solves the -scope management problem example: +Below is how this proposal solves the list example: ```sh fn list() { @@ -153,62 +153,99 @@ var body, err <= curl -f $url var name, surname, err <= getAuthor() ``` -One of the downsides of `var` is the requirement that none of the -targeted variable exists, because it makes awkward when existent -variables must be used in conjunction with new ones. An example is the -variables `$status` and `$err` that are often used to get process exit -status and errors from functions, respectively. - -The [PR #227](https://github.com/NeowayLabs/nash/pull/227) implements -this proposal but deviates in multiple assignments to handle the -downside above. The `var` statement was implemented with the rules -below: +Using var always creates new variables, shadowing previous ones, +for example: -1. At least one of the targeted variables must do not exists; -2. The existent variables are just updated in the scope it resides; - -Below are some valid examples with [#227](https://github.com/NeowayLabs/nash/pull/227): ```sh var a, b = "0", "1" # works fine, variables didn't existed before -var a, b = "2", "3" # error by rule 1 +var a, b, c = "4", "5", "6" # works! too, creating new a, b, c +``` + +On a dynamic typed language there is very little difference between +creating a new var or just reassigning it since variables are just +references that store no type information at all. For example, +what is the difference between this: -# works! c is declared but 'a' and 'b' are updated (by rule 2) -var a, b, c = "4", "5", "6" +``` +var a = "1" +a = () +``` -# works, variables first declared -var users, err <= cat /etc/passwd | awk -F ":" "{print $1}" +And this ? -# also works, but $err just updated -var pass, err <= cat /etc/shadow | awk -F ":" "{print $2}" +``` +var a = "1" +var a = () ``` -The implementation above is handy but makes the meaning of `var` -confuse because it declares new variables **and** update existent ones -(in outer scopes also). Then making hard to know what variables are -being declared local and what are being updated, by just looking at -the statement, because the meaning will depend in the current -environment of variables. +The behavior will be exactly the same, there is no semantic error +on reassigning the same variable to a value with a different type, +so reassigning on redeclaring has no difference at all (although it +makes sense for statically typed languages). -Another downside of `var` is their very incompatible nature. Every -nash script ever created will be affected. +Statements are evaluated in order, so this: -Another behavior we need to discuss is whether all variables declared -within a function (statement) will be created at the beginning -(as scope is created) or only at the time a 'var' keywork is found. +``` +a = () +var a = "1" +``` -```sh +Is **NOT** the same as this: + +``` +var a = "1" +var a = () +``` + +This is easier to understand when using closures, let's go +back to our list implementation, we had something like this: + +``` var l = () -fn test() { - l <= append($l, "1") - var l = () +fn add(val) { + # use the "l" variable from parent scope + # find first in the this scope if not found + # then find variable in the parent scope + l <= append($l, $val) } +``` + +If we write this: + +``` +var l = () -print($l) # outputs: "1" +fn add(val) { + # creates new var + var l = () + # manipulates new l var + l <= append($l, $val) +} ``` +The **add** function will not manipulate the **l** variable from the +outer scope, and our list implementation will not work properly. + +But writing this: + +``` +var l = () + +fn add(val) { + # manipulates outer l var + l <= append($l, $val) + # creates new var that is useless + var l = () +} +``` + +Will work, since we assigned a new value to the outer **l** +before creating a new **l** var. + + ## Proposal II - "outer" This proposal adds a new `outer` keyword that permits the update of From fe66ea6801c99d8d63da7a49a86c1ffa32e8cc84 Mon Sep 17 00:00:00 2001 From: Tiago Katcipis Date: Mon, 18 Sep 2017 16:05:32 -0300 Subject: [PATCH 15/20] Add some more remarks --- proposal/1-scope-management.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/proposal/1-scope-management.md b/proposal/1-scope-management.md index 14604fc2..ec3019d2 100644 --- a/proposal/1-scope-management.md +++ b/proposal/1-scope-management.md @@ -245,6 +245,14 @@ fn add(val) { Will work, since we assigned a new value to the outer **l** before creating a new **l** var. +The approach described here is very similar to how variables +are handled in [Lua](https://www.lua.org/), with the exception +that Lua uses the **local** keyword, instead of var. + +Also, Lua allows global variables to be created by default, on +Nash we prefer to avoid global stuff and produce an error when +assigning new values to variables that do not exist. + ## Proposal II - "outer" From 156571b3ceba00bc97d3815c2560bd738e3816ec Mon Sep 17 00:00:00 2001 From: Tiago Katcipis Date: Mon, 18 Sep 2017 16:15:10 -0300 Subject: [PATCH 16/20] Add some undefined behavior for outer --- proposal/1-scope-management.md | 52 ++++++++++++++++++++++++++++------ 1 file changed, 43 insertions(+), 9 deletions(-) diff --git a/proposal/1-scope-management.md b/proposal/1-scope-management.md index ec3019d2..98988782 100644 --- a/proposal/1-scope-management.md +++ b/proposal/1-scope-management.md @@ -253,12 +253,17 @@ Also, Lua allows global variables to be created by default, on Nash we prefer to avoid global stuff and produce an error when assigning new values to variables that do not exist. +Summarizing, on this proposal creating new variables is explicit +and referencing existent variables on outer scopes is implicit. -## Proposal II - "outer" + +## Proposal II - Manipulate outer scope explicitly This proposal adds a new `outer` keyword that permits the update of -variables in the outer scope. Outer assignments with non-existent -variables is an error. +variables in the outer scope. The default and implicit behavior of +variable assignments is to always create a new variable. + +Considering our list example: ```sh fn list() { @@ -280,15 +285,44 @@ fn list() { print("list: [%s]\n", $l) } - fn not_clear() { - # "l" is not cleared, but a new a new variable is created (shadowing) - # because "outer" isn't specified. - l = () - } - return $add, $get, $string } ``` The `outer` keyword has the same meaning that Python's `global` keyword. + +After an **outer** declaration all assignments will reference the +outer variable ? (like Python) + +```sh +fn list() { + # initialize an "l" variable in this scope + l = () + + fn doubleadd(val) { + # use the "l" variable from the parent + outer l <= append($l, $val) + l <= append($l, $val) + } + + return $doubleadd +} +``` + +Or it has to be repeated every time ? + +```sh +fn list() { + # initialize an "l" variable in this scope + l = () + + fn doubleadd(val) { + # use the "l" variable from the parent + outer l <= append($l, $val) + outer l <= append($l, $val) + } + + return $doubleadd +} +``` From 0148fd51c4cfe3955f179afed84b51dcdb2d1e17 Mon Sep 17 00:00:00 2001 From: Tiago Katcipis Date: Mon, 18 Sep 2017 16:36:53 -0300 Subject: [PATCH 17/20] Add a final comparison between the two approaches --- proposal/1-scope-management.md | 70 ++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/proposal/1-scope-management.md b/proposal/1-scope-management.md index 98988782..07a2c011 100644 --- a/proposal/1-scope-management.md +++ b/proposal/1-scope-management.md @@ -326,3 +326,73 @@ fn list() { return $doubleadd } ``` + +## Comparing both approaches + +As everything in life, the design space for how to handle +scope management is full of tradeoffs. + +Making outer scope management explicit makes declaring +new variables easier, since you have to type less to +create new vars. + +But managing scope using closures gets more cumbersome, +consider this nested closures with the **outer** keyword: + +```sh +fn list() { + l = () + + fn add(val) { + # use the "l" variable from the parent + outer l <= append($l, $val) + fn addagain() { + outer l <= append($l, $val) + } + return $addagain + } + + return $add +} +``` + +And this one with **var** : + +```sh +fn list() { + var l = () + + fn add(val) { + # use the "l" variable from the parent + l <= append($l, $val) + fn addagain() { + l <= append($l, $val) + } + return $addagain + } + + return $add +} +``` + +The **var** option requires more writing for the common +case of declaring new variables, but makes closures pretty +natural to write, you just manipulate the variables +that exists lexically on your scope, like you would do +inside a **if** or **for** block. + +Thinking about cognition, it seems easier to write buggy code +by forgetting to add an **outer** on the code than forgetting +to add a **var** and by mistake manipulate an variable outside +the scope. + +The decision to break if the variable does not exist also enhances +the **var** option as less buggy since no new variable will be +created if you forget the **var**, but lexically reachable variables +will be manipulated (this is ameliorated by the fact that we don't have +global variables). + +But, any statements made about cognition are really hard to be +considered as a global truth, since all human beings are biased and +identifying common patterns of cognition is really hard. But if software +design has any kind of goal, must be this =). From 83aa3899cace0aab2d610ee9f7d03fac720d14bd Mon Sep 17 00:00:00 2001 From: Tiago Katcipis Date: Mon, 18 Sep 2017 16:44:05 -0300 Subject: [PATCH 18/20] Add one more disadvantage for outer --- proposal/1-scope-management.md | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/proposal/1-scope-management.md b/proposal/1-scope-management.md index 07a2c011..e0c65ffc 100644 --- a/proposal/1-scope-management.md +++ b/proposal/1-scope-management.md @@ -392,7 +392,13 @@ created if you forget the **var**, but lexically reachable variables will be manipulated (this is ameliorated by the fact that we don't have global variables). -But, any statements made about cognition are really hard to be -considered as a global truth, since all human beings are biased and -identifying common patterns of cognition is really hard. But if software +If we go for **outer** it seems that we are going to write less, +but some code, involving closures, will be harder to read (and write). +Since code is usually read more than it is written it seems like a sensible +choice to optimize for readability and understandability than just +save a few keystrokes. + +But any statements made about cognition are really hard to be +considered as a global truth, since all human beings are biased which makes +identification of common patterns of cognition really hard. But if software design has any kind of goal, must be this =). From 4b73d06dbef43c53bde761500af157e6723845f5 Mon Sep 17 00:00:00 2001 From: Tiago Katcipis Date: Mon, 18 Sep 2017 17:12:20 -0300 Subject: [PATCH 19/20] Add more info to the comparison --- proposal/1-scope-management.md | 51 +++++++++++++++++++++++++++++----- 1 file changed, 44 insertions(+), 7 deletions(-) diff --git a/proposal/1-scope-management.md b/proposal/1-scope-management.md index e0c65ffc..a1e8b9f3 100644 --- a/proposal/1-scope-management.md +++ b/proposal/1-scope-management.md @@ -292,8 +292,8 @@ fn list() { The `outer` keyword has the same meaning that Python's `global` keyword. -After an **outer** declaration all assignments will reference the -outer variable ? (like Python) +Different from Python global, outer must appear on all assignments, +like this: ```sh fn list() { @@ -301,16 +301,15 @@ fn list() { l = () fn doubleadd(val) { - # use the "l" variable from the parent outer l <= append($l, $val) - l <= append($l, $val) + outer l <= append($l, $val) } return $doubleadd } ``` -Or it has to be repeated every time ? +This would be buggy and only add once: ```sh fn list() { @@ -318,15 +317,53 @@ fn list() { l = () fn doubleadd(val) { - # use the "l" variable from the parent - outer l <= append($l, $val) outer l <= append($l, $val) + l <= append($l, $val) } return $doubleadd } ``` +Trying to elaborate more on possible combinations +when using the **outer** keyword we get at some hard +questions, like what does outer means on this case: + +``` +fn list() { + # initialize an "l" variable in this scope + l = () + fn doubleadd(val) { + l <= append($l, $val) + outer l <= append($l, $val) + } + return $doubleadd +} +``` + +Will outer just handle the reference on its own scope or +will it jump its own scope and manipulate the outer variable ? + +The name outer implies that it will manipulate the outer scope, +bypassing its own current scope, but how do you read the outer +variable ? We would need to support something like: + +``` +fn list() { + # initialize an "l" variable in this scope + l = () + fn add(val) { + l <= "whatever" + outer l <= append(outer $l, $val) + } + return $doubleadd +} +``` + +It is like with outer we are bypassing the lexical semantics +of the code, the order of declarations is not relevant anymore +since you have a form of "goto" to jump the current scope. + ## Comparing both approaches As everything in life, the design space for how to handle From 0034e4358534789b4b9889dd58f6c053959efb88 Mon Sep 17 00:00:00 2001 From: Tiago Katcipis Date: Mon, 18 Sep 2017 17:20:00 -0300 Subject: [PATCH 20/20] Add one more regard on the interactive shell --- proposal/1-scope-management.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/proposal/1-scope-management.md b/proposal/1-scope-management.md index a1e8b9f3..153cb7f8 100644 --- a/proposal/1-scope-management.md +++ b/proposal/1-scope-management.md @@ -413,7 +413,8 @@ fn list() { ``` The **var** option requires more writing for the common -case of declaring new variables, but makes closures pretty +case of declaring new variables (specially on the interactive shell +this is pretty annoying), but makes closures pretty natural to write, you just manipulate the variables that exists lexically on your scope, like you would do inside a **if** or **for** block.