https://github.com/kanaka/mal/^ ^
I'm trying to Make Another Lisp using ciforth lina/wina/xina.
I run in a bit of trouble in the interaction between closures and recursion.
I succeeded in handling closure in
( ( (fn* (a) (fn* (fn* (b) (+ a b))) 5) 7) ... I
In lisp we have
(fn* (fn* (b) (+ a b))) 5)
Indeed is ((fn*..) 5) now evaluated in an environment where `a
is still valid.
This is done by storing the then-current
environment chain (referring to all its outer
environments) in the function structure created by the second call
of fn* and restoring prior to execution.
Of course every creation and every call will suffer a similar
overhead, so the first and the third suffer too.
However this "solution" gets me in the woods with recursion
(def! sumdown (fn* (N) (if (> N 0) (+ N (sumdown (- N 1))) 0)))
As soon as you do
(sumdown 1)
you get an environment where N:1 and you got to calculate
(+ N (sumdown (-N 1)))
The first `N is not the problem, but
now upon entering sumdown, the environment is restored/destroyed
and N is nowhere to be seen.
The solution i found is the following.
Case ...I is a weird exception. Exception! That is the keyword.
So the idea is to evaluate (fn* (b) (+ a b))) 5) as if your nose bleeds,
and then discover ERROR 8010 (symbol not found), for `a is not there.
Now you lift the not-found symbol `a from the stored environment to the
inner environment and evaluate again.
Coming back to ... I
A weird situation arises if in the meantime a function is called that
has a separate and distinct `a (free) in the environment it was
created in. Let's hope this doesn't happen.
[Why bother, people who write such programs had it coming.
The least they have to do is to rename `a to TheDelayInMSBeforeDrawingTheBottomLineInaPurpleCaptionBox
diminishing the risk for a name clash.]
On 2023-08-19, albert@cherry.(none) (albert) <albert@cherry> wrote:<SNIP>
https://github.com/kanaka/mal/^ ^
I'm trying to Make Another Lisp using ciforth lina/wina/xina.
I run in a bit of trouble in the interaction between closures and recursion. >>
I succeeded in handling closure in
( ( (fn* (a) (fn* (fn* (b) (+ a b))) 5) 7) ... I
In lisp we have
(fn* (fn* (b) (+ a b))) 5)
The same lexical variable takes on new bindings with new invocations
of its lexical scope. A closure captures the specific binding of an >activation of the scope.
--
Mastodon: @Kazinator@mstdn.ca
On 2023-08-19, albert@cherry.(none) (albert) <albert@cherry> wrote:<SNIP>
https://github.com/kanaka/mal/
I'm trying to Make Another Lisp using ciforth lina/wina/xina.
I run in a bit of trouble in the interaction between closures and recursion. >>
The invocation of a closure has to mount the captured environment,
and then extend that environment with the parameters, which
are initializd with the argument values.
The captured environment doesn't have N, of course. N is freshlyMal implements the binding by defining a nested environment where N
bound each time the function is invoked.
This extension cannot mutate the original captured environmentClarokay. The original environment is not the problem.
because that is shared.
You and MAL suggests that one can get by with one chain.The solution i found is the following.
Case ...I is a weird exception. Exception! That is the keyword.
So the idea is to evaluate (fn* (b) (+ a b))) 5) as if your nose bleeds,
and then discover ERROR 8010 (symbol not found), for `a is not there.
This is not entirely related to the "N is nowhere to be seen" problem, >because N is a lambda argument, which a is not.
Now you lift the not-found symbol `a from the stored environment to the
inner environment and evaluate again.
The usual approach is to just chain all the necessary environments
in the correct order. They are searched in that order.
The "exception" is handled inline: we didn't find the symbol in
this environment, so we continue with the parent environment
in the chain.
(Then there are flattening optimizations to reduce the search;I fear that it is worse, a mix of the two.
e.g. when a lexical closure captures a bunch of nested environments,
it's possible to flatten them into one level.)
Coming back to ... I
A weird situation arises if in the meantime a function is called that
has a separate and distinct `a (free) in the environment it was
created in. Let's hope this doesn't happen.
You seem to be sayhing, let's not bother implementing lexical closures,
and pray that the application isn't relying on them?
It seems to me you might be doing this:
1. if the evaluation of the function runs into a free variable,
an "exception" occurs.
2. The exception is "handled" by searching for the variable in
the calling function's environment.
That amounts to dynamic scope, not lexical scope.
(let ((a 3))
(define fun ()
a))
(let ((a 4)) ...II
(fun)) -> 4
Here, fun has not actually captured any environment. When (fun) is
invoked, a is not found. This triggers an "exception" which is handled
by finding the (a 4) binding in the environment that is valid at the
time of the call.
This is called dynamic scope, not lexical.
Old Lisp is like this: MacCarthy's LISP 1, LISP 1.5.
[Why bother, people who write such programs had it coming.
The least they have to do is to rename `a to
TheDelayInMSBeforeDrawingTheBottomLineInaPurpleCaptionBox
diminishing the risk for a name clash.]
Programs with escaping closures are written all the time. Not just in
Lisp, but in Javascript, Python and other languages.
It's become and indispensable feature.Not solved but at least mitigated.
The problem is not solved by renaming.
Yes, under dynamic scoping you have to use namespacing or renaming toThis is the familiar problem in C, where a global variable `i is
avoid accidental clashes between truly global variables and locals.
<SNIP>DROP : ISN'T UNIQUE <<<<<<<<<<<<<<<<<<<<
Mastodon: @Kazinator@mstdn.ca
In article <20230819073816.564@kylheku.com>,
Kaz Kylheku <864-117-4973@kylheku.com> wrote:
On 2023-08-19, albert@cherry.(none) (albert) <albert@cherry> wrote:<SNIP>
https://github.com/kanaka/mal/
I'm trying to Make Another Lisp using ciforth lina/wina/xina.
I run in a bit of trouble in the interaction between closures and recursion.
The invocation of a closure has to mount the captured environment,
and then extend that environment with the parameters, which
are initializd with the argument values.
This puzzles me. That means that a function body can not refer
to data in the current environment only to data in the captured
environment.
If a compiler can instantiate one occurence of a, that must be
a global variable. No compiler since Algol instantiates local
variables. However, compilers for lexical scopes can allocate
a local variablein a fixed frame location.
This is particularly simple in lexically scoped languages without
lexical closures.
For instance, in C, when a function is invoked, just the stack/frame
pointers have to be moved to create space for all the variables in
all the lexical scopes in that function flatted into one block,
in which all the variables ahve fixed offsets relative to the frame
pointer.
In article <20230819073816.564@kylheku.com>,
Kaz Kylheku <864-117-4973@kylheku.com> wrote:
On 2023-08-19, albert@cherry.(none) (albert) <albert@cherry> wrote:<SNIP>
https://github.com/kanaka/mal/
I'm trying to Make Another Lisp using ciforth lina/wina/xina.
I run in a bit of trouble in the interaction between closures and recursion.
The invocation of a closure has to mount the captured environment,
and then extend that environment with the parameters, which
are initializd with the argument values.
This puzzles me. That means that a function body can not refer
to data in the current environment only to data in the captured
environment.
The usual approach is to just chain all the necessary environmentsYou and MAL suggests that one can get by with one chain.
in the correct order. They are searched in that order.
The "exception" is handled inline: we didn't find the symbol in
this environment, so we continue with the parent environment
in the chain.
What is then this correct order?
Is that by any chance
outer original environment
{optional others}
* captured environment,
* environment that contains the parameter bindings.
In a recursive call the last two repeat.
If I understand it correctly,
MAL is talking about capturing the environment
so MAL is aiming at lexical scope
so if the test marked .. II gives the result specified,
then I have failed at implementing (this aspect of) MAL.
Programs with escaping closures are written all the time. Not just in >>Lisp, but in Javascript, Python and other languages.
Of course you are right. But I prefer language where the compiler instantiates the occurances of a, so that there are no confusion.
It's become and indispensable feature.Not solved but at least mitigated.
The problem is not solved by renaming.
Yes, under dynamic scoping you have to use namespacing or renaming toThis is the familiar problem in C, where a global variable `i is
avoid accidental clashes between truly global variables and locals.
declared, and you run into problems using `i in local loops without
a shadowing declaration.
https://github.com/kanaka/mal/
I'm trying to Make Another Lisp using ciforth lina/wina/xina.
I run in a bit of trouble in the interaction between closures and recursion.
( ( (fn* (a) (fn* (fn* (b) (+ a b))) 5) 7) ... I
In lisp we have
(fn* (fn* (b) (+ a b))) 5)
(def! sumdown (fn* (N) (if (> N 0) (+ N (sumdown (- N 1))) 0))) ...I
In article <nnd$108a8f87$6cca2ba5@1b35385dda51f7cd>,
none) (albert <albert@cherry.> wrote:
https://github.com/kanaka/mal/
I'm trying to Make Another Lisp using ciforth lina/wina/xina.
I run in a bit of trouble in the interaction between closures and recursion. >>
( ( (fn* (a) (fn* (fn* (b) (+ a b))) 5) 7) ... I
In lisp we have
(fn* (fn* (b) (+ a b))) 5)
(def! sumdown (fn* (N) (if (> N 0) (+ N (sumdown (- N 1))) 0))) ...I
The above now passes the test of MAL.
This is what I do.
Assuming MAL is intended to be lexical, first look up symbols
in the parameter mapping,
then in the environment stored in the
closure, then in the environment where the call is made.
All environments can recursively refer to outer environments,
not the parameter mapping.
On 2023-08-22, albert@cherry.(none) (albert) <albert@cherry> wrote:
In article <nnd$108a8f87$6cca2ba5@1b35385dda51f7cd>,
none) (albert <albert@cherry.> wrote:
https://github.com/kanaka/mal/
I'm trying to Make Another Lisp using ciforth lina/wina/xina.
I run in a bit of trouble in the interaction between closures and recursion. >>>
( ( (fn* (a) (fn* (fn* (b) (+ a b))) 5) 7) ... I
In lisp we have
(fn* (fn* (b) (+ a b))) 5)
(def! sumdown (fn* (N) (if (> N 0) (+ N (sumdown (- N 1))) 0))) ...I
The above now passes the test of MAL.
This is what I do.
Assuming MAL is intended to be lexical, first look up symbols
in the parameter mapping,
then in the environment stored in the
closure, then in the environment where the call is made.
All environments can recursively refer to outer environments,
not the parameter mapping.
If "environment where the call is made" in the last step there
refers to the lexical environment within the calling function,
you have a mistake.
Under lexical scope, an invoked function must not see variables
in the caller.
If an identifier is not found in the lexical scope, you can have a
fallback on a pervasive scope (for finding global variables).
If you're seeing locals in parent functions, you have dynamic
scope; and if taht is combined with closures that capture
environments, you have something that can be called "dynamic closurees".
It's a valid implementation choice; it's just not to be confused with
lexical scope.
If you have dynamic closures, then some test cases for lexical scope
will pass.
What will not pass are tests which tests for these things:
- error checking: validate that a free variable references
in a function which has the same name as a local variable
in a caller is diagnosed as an undefined reference,
rather than referring to the parent.
- interference: validate that if a callee assigns to a variable
x which happens to be a local variable in the caller,
it doesn't clobber the caller's variable.
Part of the motivation for lexical scope is encapsulation.
All accesses to a lexical variable are possible only from
the scope where it is visible, and nowhere else.
Mastodon: @Kazinator@mstdn.ca
It seems to me you might be doing this:
1. if the evaluation of the function runs into a free variable,
an "exception" occurs.
2. The exception is "handled" by searching for the variable in
the calling function's environment.
That amounts to dynamic scope, not lexical scope.
(let ((a 3))
(define fun ()
a))
(let ((a 4))
(fun)) -> 4
Here, fun has not actually captured any environment. When (fun) is
invoked, a is not found. This triggers an "exception" which is handled
by finding the (a 4) binding in the environment that is valid at the
time of the call.
Mastodon: @Kazinator@mstdn.ca
I tested it, and my implementation fails the dynamic scope test.
But what is supposed to happen if `a is global (pervasive environment)
here?
(define a 12)
(define fun () a)
(define a 13)
(fun)
I have README's in different directories. I don't identify those, but I could.
There is a list of filenames, and as soon as I change directory the i-node of the README in this directory is filled in in the list.
Is this the way the name `a is handled?
On 2023-08-26, albert@cherry.(none) (albert) <albert@cherry> wrote:
I tested it, and my implementation fails the dynamic scope test.
But what is supposed to happen if `a is global (pervasive environment)
here?
(define a 12)
(define fun () a)
(define a 13)
(fun)
If there is a top-level environment like in Common Lisp, then the
second definition of a is actually an assignment.
If there is a top-level lexical scope, then the second a is a new
lexical identifier unrelated to the first a, and so fun refers
to the 12 not the 13.
This is a just a matter of knowing the requirements in MAL.
Mastodon: @Kazinator@mstdn.ca
On Thu, 31 Aug 2023 11:37:50 +0200
albert@cherry.(none) (albert) wrote:
In article <e1jFR1e0dJXio6H8+@bongo-ra.co>,
[...]
I have no requirements for lisp. The MAL challenge is not to
implement a lisp, but use a particular language (Forth FORTRAN C
python go list-this lisp-that) to investigate the strong and weak
side of the implementation language for implementing it.
The attraction of the MAL side is that it has step-by-step
test driven development. Skipping MAL features is just cheating.
[...]
I have done a lisp in Forth
https://github.com/albertvanderhorst/forthlisp
Then what's the point of doing MAL ? Just to compare your code with that
by other people in other languages ?
Sysop: | Keyop |
---|---|
Location: | Huddersfield, West Yorkshire, UK |
Users: | 300 |
Nodes: | 16 (2 / 14) |
Uptime: | 54:15:04 |
Calls: | 6,712 |
Files: | 12,243 |
Messages: | 5,355,326 |