Certainly locals are easier and folks find ways of justifying them.
My concern in using them would be what have I missed that's simpler?
dxforth <dxf...@gmail.com> writes:
Certainly locals are easier and folks find ways of justifying them.One interesting case is the pure-stack closures of Gforth: It started
My concern in using them would be what have I missed that's simpler?
out with me wanting to provide better support for the xt-passing style
(as used frequently in functional programming languages).
One problem that Forth (and even more so, C) has for this style is
that there is no simple way to pass data that is not designed into the interface. E.g., if the word you call expects an xt with the stack
effect ( n1 -- n2 ), and you want to pass [: n + ;] as this xt, where
n is determined at run-time, how do you pass n?
The solution of languages with lexically-scoped locals (e.g., Scheme)
is to pass n through the lexical-scoping mechanism, resulting in
something like the following:
On Wednesday, April 5, 2023 at 3:50:16 AM UTC-5, Anton Ertl wrote:
dxforth <dxf...@gmail.com> writes:
Certainly locals are easier and folks find ways of justifying them.One interesting case is the pure-stack closures of Gforth: It started
My concern in using them would be what have I missed that's simpler?
out with me wanting to provide better support for the xt-passing style
(as used frequently in functional programming languages).
One problem that Forth (and even more so, C) has for this style is
that there is no simple way to pass data that is not designed into the interface. E.g., if the word you call expects an xt with the stack
effect ( n1 -- n2 ), and you want to pass [: n + ;] as this xt, where
n is determined at run-time, how do you pass n?
The solution of languages with lexically-scoped locals (e.g., Scheme)In zeptoforth I have opted for the following:
is to pass n through the lexical-scoping mechanism, resulting in
something like the following:
closure import \ Yeah I know these are not closures per se, because they do not involve name-binding per se
closure-size buffer: my-xt \ This will be ( n -- n ) once DO-BIND is executed
: do-bind { n -- } n my-xt [: { n } n + ;] bind ;
1 do-bind
0 my-xt execute . \ Outputs: 1 ok
1 my-xt execute . \ Outputs: 2 ok
2 my-xt execute . \ Outputs: 3 ok
2 do-bind
0 my-xt execute . \ Outputs: 2 ok
1 my-xt execute . \ Outputs: 3 ok
2 my-xt execute . \ Outputs: 4 ok
\ etc.
While BIND, and its relatives 2BIND and NBIND, do not bind names per se, they initialize xt's to which values are bound, being passed to the executed code on the data stack, until they are executed again with the same xt, where the values will be rebound.
These of course can be used within words, as seen below:
closure import
create my-array here $DE c, $AD c, $BE c, $EF c,
4 constant my-array-len
: test-iter ( n -- )
closure-size [: { n my-xt }
n my-xt [: { byte n } byte n + h.2 ;] bind
my-array my-array-len my-xt citer
;] with-aligned-allot
;
0 test-iter \ Outputs: DEADBEEF ok
1 test-iter \ Outputs: DFAEBFF0 ok
-1 test-iter \ Outputs: DDACBDEE ok
This is not the nicest way of doing things, but without using heap allocation,
something I much prefer to avoid, or hard-coding placing closures in the dictionary (e.g. what if something wants to do something else with the dictionary, or what if someone wants to store a closure in a structure or object?), something I also prefer to avoid, this seems like the best way
to do this to me.
Travis
create my-array here $DE c, $AD c, $BE c, $EF c,
4 constant my-array-len
On Wednesday, April 5, 2023 at 12:04:41 PM UTC-5, Travis Bemann wrote:Forth made me value local variables. You can run code "10x" faster and "10x" smaller, but at the cost of taking "10x" and making "10x" more mistakes.
On Wednesday, April 5, 2023 at 3:50:16 AM UTC-5, Anton Ertl wrote:
dxforth <dxf...@gmail.com> writes:
Certainly locals are easier and folks find ways of justifying them.
My concern in using them would be what have I missed that's simpler? One interesting case is the pure-stack closures of Gforth: It started out with me wanting to provide better support for the xt-passing style (as used frequently in functional programming languages).
One problem that Forth (and even more so, C) has for this style is
that there is no simple way to pass data that is not designed into the interface. E.g., if the word you call expects an xt with the stack effect ( n1 -- n2 ), and you want to pass [: n + ;] as this xt, where
n is determined at run-time, how do you pass n?
The solution of languages with lexically-scoped locals (e.g., Scheme)In zeptoforth I have opted for the following:
is to pass n through the lexical-scoping mechanism, resulting in something like the following:
closure import \ Yeah I know these are not closures per se, because they do not involve name-binding per se
closure-size buffer: my-xt \ This will be ( n -- n ) once DO-BIND is executed
: do-bind { n -- } n my-xt [: { n } n + ;] bind ;
1 do-bind
0 my-xt execute . \ Outputs: 1 ok
1 my-xt execute . \ Outputs: 2 ok
2 my-xt execute . \ Outputs: 3 ok
2 do-bind
0 my-xt execute . \ Outputs: 2 ok
1 my-xt execute . \ Outputs: 3 ok
2 my-xt execute . \ Outputs: 4 ok
\ etc.
While BIND, and its relatives 2BIND and NBIND, do not bind names per se, they initialize xt's to which values are bound, being passed to the executed
code on the data stack, until they are executed again with the same xt, where the values will be rebound.
These of course can be used within words, as seen below:
closure import
create my-array here $DE c, $AD c, $BE c, $EF c,
4 constant my-array-len
: test-iter ( n -- )
closure-size [: { n my-xt }
n my-xt [: { byte n } byte n + h.2 ;] bind
my-array my-array-len my-xt citer
;] with-aligned-allot
;
0 test-iter \ Outputs: DEADBEEF ok
1 test-iter \ Outputs: DFAEBFF0 ok
-1 test-iter \ Outputs: DDACBDEE ok
This is not the nicest way of doing things, but without using heap allocation,
something I much prefer to avoid, or hard-coding placing closures in the dictionary (e.g. what if something wants to do something else with the dictionary, or what if someone wants to store a closure in a structure or object?), something I also prefer to avoid, this seems like the best way to do this to me.
TravisMinor correction:
create my-array here $DE c, $AD c, $BE c, $EF c,should be:
4 constant my-array-len
create my-array here $DE c, $AD c, $BE c, $EF c,
here swap - constant my-array-len
Forth made me value local variables. You can run code "10x" faster and "10x" smaller, but at the cost of taking "10x" and making "10x" more mistakes.
A very simple design feature that saved me a lot of pain
On Wednesday, April 5, 2023 at 1:23:32 PM UTC-5, fabianor...@gmail.com wrote:
Forth made me value local variables. You can run code "10x" faster and "10x" smaller, but at the cost of taking "10x" and making "10x" more mistakes.I don't think that doing things via traditional stack operations is faster. As I mention, when
A very simple design feature that saved me a lot of pain
storing local variables on the return stack, fetching a local variable is just three instructions
and storing a local variable is a mere two instructions on ARMv6-M. The only stack
operation that is nearly that fast is PICK which, with constant folding, can be optimized
in many cases down to three instructions on ARMv6-M. But to do any complex operations
on the data stack ends up requiring many stack operations such as SWAP's, ROT's,
2SWAP's, 2DUP's, and so on, which quickly add up, especially when compiling to native
code (unless you have a register-assigning compiler, like Mecrisp-Stellaris's, and even then
Mecrisp-Stellaris has to evict stack entries stored in registers whenever you call a word that
cannot be inlined). Consequently, for complex code, I would expect local variables stored
on the return stack to be at least as fast if not faster than traditional data stack operation
with its associated stack churn on an architecture such as ARMv6-M (I would expect even
better performance on ARMv7-M because fetching a local variable can be reduced to
two instructions on that architecture).
On Wednesday, April 5, 2023 at 3:50:16=E2=80=AFAM UTC-5, Anton Ertl wrote:
dxforth <dxf...@gmail.com> writes:=20
Certainly locals are easier and folks find ways of justifying them.=20One interesting case is the pure-stack closures of Gforth: It started=20
My concern in using them would be what have I missed that's simpler?
out with me wanting to provide better support for the xt-passing style=20
(as used frequently in functional programming languages).=20
=20
One problem that Forth (and even more so, C) has for this style is=20
that there is no simple way to pass data that is not designed into the=20
interface. E.g., if the word you call expects an xt with the stack=20
effect ( n1 -- n2 ), and you want to pass [: n + ;] as this xt, where=20
n is determined at run-time, how do you pass n?=20
=20
The solution of languages with lexically-scoped locals (e.g., Scheme)=20
is to pass n through the lexical-scoping mechanism, resulting in=20
something like the following:=20
In zeptoforth I have opted for the following:
closure import \ Yeah I know these are not closures per se, because they do=
not involve name-binding per se
closure-size buffer: my-xt \ This will be ( n -- n ) once DO-BIND is execut= >ed
: do-bind { n -- } n my-xt [: { n } n + ;] bind ;
1 do-bind
0 my-xt execute . \ Outputs: 1 ok
This is not the nicest way of doing things, but without using heap allocati= >on,
something I much prefer to avoid, or hard-coding placing closures in the >dictionary (e.g. what if something wants to do something else with the >dictionary, or what if someone wants to store a closure in a structure or >object?), something I also prefer to avoid, this seems like the best way
to do this to me.
Anton Ertl schrieb am Donnerstag, 6. April 2023 um 09:22:49 UTC+2:
Closures can be allocated in the Dictionary (D), on the heap (H), or
on the locals stack (L), or you can use an allocation word you pass as
xt. Given that our closures can be arbitrarily large, I cannot think
of a comfortable interface that uses a pre-allocated buffer.
Given that closures can be called somewhere and somewhen later,
even when the enclosing word has already terminated, I am wondering
how the locals stack comes into play here.
Closures can be allocated in the Dictionary (D), on the heap (H), or
on the locals stack (L), or you can use an allocation word you pass as
xt. Given that our closures can be arbitrarily large, I cannot think
of a comfortable interface that uses a pre-allocated buffer.
dxforth <dxforth@gmail.com> writes:[...]
Certainly locals are easier and folks find ways of justifying them.
My concern in using them would be what have I missed that's simpler?
One interesting case is the pure-stack closures of Gforth
In the presentation of this work at EuroForth 2018, I mentioned that
one could get rid of the locals here completely by just passing a
certain number of stack items from the closure construction to the
closure execution time. In my presentation I suggested a syntax like:
: n+xt ( n -- xt )
1 0 [:d + ;] ;
where the "1 0" says that 1 cell and 0 FP-stack items are passed to
the closure. This was just a side comment, but some time later Bernd
Paysan implemented this idea with the following syntax:
: n+xt ( n -- xt )
[n:d + ;] ;
The "n" in "[n:d]" indicates a single cell; you can also use "d"
(double cell), or "f" (one FP-stack item). This works in development
Gforth, and is used in some places.
This idea seems obvious in hindsight, given the requirements, but it
was not at all obvious at the start, and, e.g., it's not present in
Joy (a stack-based functional language without locals).
We would have missed this idea if we had rejected everything
to do with locals from the start.
Travis Bemann <tabe...@gmail.com> writes:
On Wednesday, April 5, 2023 at 3:50:16=E2=80=AFAM UTC-5, Anton Ertl wrote: >> dxforth <dxf...@gmail.com> writes:=20
Certainly locals are easier and folks find ways of justifying them.=20One interesting case is the pure-stack closures of Gforth: It started=20 >> out with me wanting to provide better support for the xt-passing style=20 >> (as used frequently in functional programming languages).=20
My concern in using them would be what have I missed that's simpler?
=20
One problem that Forth (and even more so, C) has for this style is=20
that there is no simple way to pass data that is not designed into the=20 >> interface. E.g., if the word you call expects an xt with the stack=20
effect ( n1 -- n2 ), and you want to pass [: n + ;] as this xt, where=20 >> n is determined at run-time, how do you pass n?=20
=20
The solution of languages with lexically-scoped locals (e.g., Scheme)=20 >> is to pass n through the lexical-scoping mechanism, resulting in=20
something like the following:=20
In zeptoforth I have opted for the following:
closure import \ Yeah I know these are not closures per se, because they do=I also call them closures, because they provide the same functionality
not involve name-binding per se
that closures provide in statically scoped languages.
closure-size buffer: my-xt \ This will be ( n -- n ) once DO-BIND is execut= >edAnything wrong with
: do-bind { n -- } n my-xt [: { n } n + ;] bind ;
: do-bind ( n -- ) my-xt [: + ;] bind ;
?
1 do-bindThis assumes that the start address of the data structure written by
0 my-xt execute . \ Outputs: 1 ok
BIND also serves as xt. In development Gforth the xt of an anonymous
word ist two cells after the start of the data structure.
This is not the nicest way of doing things, but without using heap allocati= >on,This is the classic Forth technique of letting the caller provide a
something I much prefer to avoid, or hard-coding placing closures in the >dictionary (e.g. what if something wants to do something else with the >dictionary, or what if someone wants to store a closure in a structure or >object?), something I also prefer to avoid, this seems like the best way >to do this to me.
buffer that the callee fills. And given that here the buffer size is
known in advance, that's not a bad solution (for an example of badness
when the size is not known, take a look at READ-LINE). Nevertheless,
in Gforth we took a different approach:
Closures can be allocated in the Dictionary (D), on the heap (H), or
on the locals stack (L), or you can use an allocation word you pass as
xt. Given that our closures can be arbitrarily large, I cannot think
of a comfortable interface that uses a pre-allocated buffer.
dxforth <dxforth@gmail.com> writes:
Certainly locals are easier and folks find ways of justifying them.
My concern in using them would be what have I missed that's simpler?
One interesting case is the pure-stack closures of Gforth: It started
out with me wanting to provide better support for the xt-passing style
(as used frequently in functional programming languages).
One problem that Forth (and even more so, C) has for this style is
that there is no simple way to pass data that is not designed into the interface. E.g., if the word you call expects an xt with the stack
effect ( n1 -- n2 ), and you want to pass [: n + ;] as this xt, where
n is determined at run-time, how do you pass n?
On 2023-04-05 07:33, Anton Ertl wrote:
dxforth <dxforth@gmail.com> writes:[...]
Certainly locals are easier and folks find ways of justifying them.
My concern in using them would be what have I missed that's simpler?
One interesting case is the pure-stack closures of Gforth
In the presentation of this work at EuroForth 2018, I mentioned that
one could get rid of the locals here completely by just passing a
certain number of stack items from the closure construction to the
closure execution time. In my presentation I suggested a syntax like:
: n+xt ( n -- xt )
1 0 [:d + ;] ;
where the "1 0" says that 1 cell and 0 FP-stack items are passed to
the closure. This was just a side comment, but some time later Bernd
Paysan implemented this idea with the following syntax:
: n+xt ( n -- xt )
[n:d + ;] ;
The "n" in "[n:d]" indicates a single cell; you can also use "d"
(double cell), or "f" (one FP-stack item). This works in development
Gforth, and is used in some places.
This idea seems obvious in hindsight, given the requirements, but it
was not at all obvious at the start, and, e.g., it's not present in
Joy (a stack-based functional language without locals).
We would have missed this idea if we had rejected everything to do
with locals from the start.
I cannot agree. As I already mentioned [1], this idea is actually the conception of partial application, which is completely independent of
local variables. I use partial application a lot in Forth.
Perhaps the main difference between partially applied functions and
closures is following.
— A partially applied function is created by binding anonymous
*positional* parameters.
— A closure (a lexical closure) is created by binding *named* objects
from the lexical environment.
Obviously, partial application can be implemented using closures. But it isn't necessary. And the former is far simpler than the later, because
having locals, the same name from the lexical environment can be
resolved to different objects at different run-time points.
On 2023-04-06 13:04, Ruvim wrote:
On 2023-04-05 07:33, Anton Ertl wrote:
dxforth <dxf...@gmail.com> writes:[...]
Certainly locals are easier and folks find ways of justifying them.
My concern in using them would be what have I missed that's simpler?
One interesting case is the pure-stack closures of Gforth
BTW, partial application is available in Joy via "cons". In Cat it'sIn the presentation of this work at EuroForth 2018, I mentioned that
one could get rid of the locals here completely by just passing a
certain number of stack items from the closure construction to the
closure execution time. In my presentation I suggested a syntax like:
: n+xt ( n -- xt )
1 0 [:d + ;] ;
where the "1 0" says that 1 cell and 0 FP-stack items are passed to
the closure. This was just a side comment, but some time later Bernd
Paysan implemented this idea with the following syntax:
: n+xt ( n -- xt )
[n:d + ;] ;
The "n" in "[n:d]" indicates a single cell; you can also use "d"
(double cell), or "f" (one FP-stack item). This works in development
Gforth, and is used in some places.
This idea seems obvious in hindsight, given the requirements, but it
was not at all obvious at the start, and, e.g., it's not present in
Joy (a stack-based functional language without locals).
present as "papply".
In Joy:
DEFINE np_xt == [ + ] cons.
1 np_xt dup . \ prints "[1 +]"
2 swap i . \ prints "3"
We would have missed this idea if we had rejected everything to do
with locals from the start.
I cannot agree. As I already mentioned [1], this idea is actually the conception of partial application, which is completely independent of local variables. I use partial application a lot in Forth.
Perhaps the main difference between partially applied functions and closures is following.
— A partially applied function is created by binding anonymous *positional* parameters.
— A closure (a lexical closure) is created by binding *named* objects from the lexical environment.
Obviously, partial application can be implemented using closures. But it isn't necessary. And the former is far simpler than the later, because having locals, the same name from the lexical environment can beA closure usually means a lexical closure.
resolved to different objects at different run-time points.
Without local variables in general, or without *ability* to bind the
outer local variables, closures are trivial. I'm not sure it's still
correct to call such functions "closures". Therefor, in Factor, Joy,
Cat, Forth, such functions are intentionally called "quotations".
If we don't call quotations "closures", then we rather shouldn't call partially applied functions "closures" too.
Also, if the outer local variables are not bound in a function, it's probably confusing if this function would be called "closure".
Well, if we still want to call partially applied functions "closures",
they shouldn't be "lexical closures", but some other kind of closures.
And "pure-stack closures" is not well suited either, I think.
BTW, partial application is available in Joy via "cons".
In Cat it's
present as "papply".\
Without local variables in general, or without *ability* to bind the
outer local variables, closures are trivial. I'm not sure it's still
correct to call such functions "closures". Therefor, in Factor, Joy,
Cat, Forth, such functions are intentionally called "quotations".
If we don't call quotations "closures", then we rather shouldn't call >partially applied functions "closures" too.
Also, if the outer local variables are not bound in a function, it's
probably confusing if this function would be called "closure".
Well, if we still want to call partially applied functions "closures",
they shouldn't be "lexical closures", but some other kind of closures.
And "pure-stack closures" is not well suited either, I think.
On 2023-04-05 07:33, Anton Ertl wrote:
We would have missed this idea if we had rejected everything
to do with locals from the start.
I cannot agree.
As I already mentioned [1], this idea is actually the
conception of partial application, which is completely independent of
local variables.
— A closure (a lexical closure) is created by binding *named* objects
from the lexical environment.
So a problem is how to create a partially applied function dynamically,
and how to free resources of an already unneeded function
minforth <minf...@arcor.de> writes:
But I am still struggling with finding good applicationsDon't struggle! If the need does not come to you on its own, you
don't need them.
But I am still struggling with finding good applications
Anton Ertl schrieb am Freitag, 7. April 2023 um 14:17:54 UTC+2:
minforth <minf...@arcor.de> writes:That zero investment so far suits me well. ;-)
But I am still struggling with finding good applicationsDon't struggle! If the need does not come to you on its own, you
don't need them.
"Look, Ma, I have a clever solution! All I need now is a nice problem!"
On Friday, April 7, 2023 at 8:08:15 AM UTC-5, minforth wrote:
Anton Ertl schrieb am Freitag, 7. April 2023 um 14:17:54 UTC+2:
minforth <minf...@arcor.de> writes:That zero investment so far suits me well. ;-)
But I am still struggling with finding good applicationsDon't struggle! If the need does not come to you on its own, you
don't need them.
"Look, Ma, I have a clever solution! All I need now is a nice problem!"The application for closures/partial application/whatever you want
to call it in Forth that I have had is if I want to associate an address
of a data structure or an ID associated with a peripheral with a handler word (e.g. an interrupt handler) without hard-coding it within the handler (case in point, what if you have a word for handling multiple different peripherals, each with their own IRQ?). Before I added support for closures/partial application/etc. what I had to do was to manually write words that hard-code the associated data and then call the actual
handler; if I ever revisit that code I will change it to use closures/partial
application/etc.
Travis Bemann schrieb am Freitag, 7. April 2023 um 18:38:07 UTC+2:
On Friday, April 7, 2023 at 8:08:15 AM UTC-5, minforth wrote:
Anton Ertl schrieb am Freitag, 7. April 2023 um 14:17:54 UTC+2:
minforth <minf...@arcor.de> writes:That zero investment so far suits me well. ;-)
But I am still struggling with finding good applicationsDon't struggle! If the need does not come to you on its own, you
don't need them.
I think lots of applications for closures comprise some capturing of context (data) and using that captured context later. Starting and stopping a task in"Look, Ma, I have a clever solution! All I need now is a nice problem!"The application for closures/partial application/whatever you want
to call it in Forth that I have had is if I want to associate an address of a data structure or an ID associated with a peripheral with a handler word (e.g. an interrupt handler) without hard-coding it within the handler (case in point, what if you have a word for handling multiple different peripherals, each with their own IRQ?). Before I added support for closures/partial application/etc. what I had to do was to manually write words that hard-code the associated data and then call the actual
handler; if I ever revisit that code I will change it to use closures/partial
application/etc.
"my" world for example. Things one could also achieve with OO methods.
Although using closure xt's (aka anonymous functions) can make you code rather concise. But can make it also read-only, unless there is a commonly understood syntax.
The unfortunate thing about using closures/partial application in zeptoforth is that it can be rather cumbersome. For starters, you have to allot/allocate storage for them which will remain available when they are actually called.
Travis Bemann schrieb am Freitag, 7. April 2023 um 21:38:17 UTC+2:
The unfortunate thing about using closures/partial application in zeptoforthIOW a closure is a data instance of upvalues with an anonymous function.
is that it can be rather cumbersome. For starters, you have to allot/allocate
storage for them which will remain available when they are actually called.
If you don't want garbage collection, you could hide a destructor within the function. But then, why not use OO in the first place?
On Friday, April 7, 2023 at 3:39:35 PM UTC-5, minforth wrote:
Travis Bemann schrieb am Freitag, 7. April 2023 um 21:38:17 UTC+2:With OO (which zeptoforth supports) the obvious approach is to make the closure a member in the object which it passes to the handler itself, so it has
The unfortunate thing about using closures/partial application in zeptoforthIOW a closure is a data instance of upvalues with an anonymous function. If you don't want garbage collection, you could hide a destructor within the
is that it can be rather cumbersome. For starters, you have to allot/allocate
storage for them which will remain available when they are actually called.
function. But then, why not use OO in the first place?
the same lifetime as said object itself. Even if one is not using the OO layer
per se, the same goes for storing a closure in a structure which it itself binds.
This is less flexible, however, than the garbage-collected closure approach that is perennial in the functional programming world, which uses closures far more pervasively. Garbage collection, though, is not an option in zeptoforth as it is designed for embedded control; while zeptoforth does have support for heaps, even heaps are completely optional, having to be deliberately constructed by the user (and also there is no "the heap" - heaps
are just another data structure, so the user can be more than one of them), the only heap that is supported "out of the box" is a small one used for storing history for the line editor. Yes, the likes of MicroPython and eLua use garbage-collected heaps, these hurt their real-time characteristics,
and make them unsuitable for actual real-time operation.
... the likes of MicroPython and eLua
use garbage-collected heaps, these hurt their real-time characteristics,
and make them unsuitable for actual real-time operation.
Ruvim <ruvim.pinka@gmail.com> writes:[...]
As I already mentioned [1], this idea is actually the
conception of partial application, which is completely independent of
local variables.
Or of currying; from the caller's perspective the difference is small <https://en.wikipedia.org/wiki/Currying#Contrast_with_partial_function_application>.
Plus, I had not known the term before, although it sounded familiar: <https://en.wikipedia.org/wiki/Partial_application> starts by saying:
|Not to be confused with partial evaluation
and so I found out that I was thinking about "partial evaluation" when
you wrote "partial application".
Ruvim <ruvim.pinka@gmail.com> writes:
On 2023-04-05 07:33, Anton Ertl wrote:
We would have missed this idea if we had rejected everything
to do with locals from the start.
I cannot agree.
You were not involved.
— A closure (a lexical closure) is created by binding *named* objects >>from the lexical environment.
In Forth we do not have a lexical environment.
So we use the term
"closure" to refer to the thing that is used for the purposes that
closures are used for in languages that have a lexical environment.
You may prefer to call it a "partially-applied colon definition", but "closure" has the advantage of being much shorter.
So a problem is how to create a partially applied function dynamically,
and how to free resources of an already unneeded function
It's pretty straightforward using "carnal knowledge", and as you
demonstrate, pretty messy if you want to use currently standard means;
so I think the better question is: What is missing from standard
Forth?
Is there something that is currently "carnal knowledge", but
should be standardized, or should we standardize nothing at a lower
level, and intead standardize the partial application word. (Probably
at the present time the answer is that there is not enough common
practice to standardize anything, but let's assume that at some point
this will change.)
So we use the term
"closure" to refer to the thing that is used for the purposes that
closures are used for in languages that have a lexical environment.
On 2023-04-07 11:20, Anton Ertl wrote:
In Forth we do not have a lexical environment.Sure, we have. Formally, it depends on particular definition of
"lexical environment" term.
Intuitively, lexical environment is a set of information that is used to recognize lexemes (or, on which recognizing of lexemes depends on).
Without local variables in general, or without *ability* to bind the
outer local variables, closures are trivial. I'm not sure it's still
correct to call such functions "closures". Therefor, in Factor, Joy,
Cat, Forth, such functions are intentionally called "quotations".
If we don't call quotations "closures", then we rather shouldn't call
partially applied functions "closures" too.
That does not follow. Quotations neither are closures nor do they
serve the purpose of closures.
What Gforth calls closures is intended
to be used for the purpose that closures (as well as curried and partially-applied functions) are used for in Scheme; and if we have
[n:d + ;]
(what I call a pure-stack closure), it's not a partially-applied (or partially-EXECUTEd?) colon definition, because no partial application/execution has happened, and it's not a colon definition.
It might be called a curried quotation if you are unhappy with
"closure", but I am happy with "closure" (but a also call the
resulting xt the xt of a closure, so I am quite loose in terminology
here.
Also, if the outer local variables are not bound in a function, it's
probably confusing if this function would be called "closure".
About as confusing as calling a colon definition a "function".
Well, if we still want to call partially applied functions "closures",
they shouldn't be "lexical closures", but some other kind of closures.
"Flat closure" or "Gforth closure" is fine with me.
And "pure-stack closures" is not well suited either, I think.
"Pure-stack" contrasts with the closures that have locals on the
inside:
pure-stack closure: [n:d + ;]
other Gforth closure: [{: n :}d n + ;]
Closures that are created in the same lexical environment, share the
same external variables (their state).
For example, see in JavaScript:
x = (function (){
var a ; a = 0;
return [ ()=>{ a+=1; return a}, ()=>{ a+=10; return a} ]
})();
x[0]() // "1"
x[1]() // "11"
x[0]() // "12"
x[0]() // "13"
x[1]() // "23"
Gforth "closures" does not provide this functionality. Because actually
they are partially applied functions (partially applied definitions, if
you like), which are defined in special syntax.
On 2023-04-07 11:51, Anton Ertl wrote:
What Gforth calls closures is intended
to be used for the purpose that closures (as well as curried and
partially-applied functions) are used for in Scheme; and if we have
[n:d + ;]
(what I call a pure-stack closure), it's not a partially-applied (or
partially-EXECUTEd?) colon definition, because no partial
application/execution has happened, and it's not a colon definition.
Partial application happens when a new xt is returned by this construct.
The construct
[n:d + ;]
is actually a sugar over
[: + ;] partial1
Where "partial1 ( x1 xt1 -- xt )" performs partial application.
Closures that are created in the same lexical environment, share the
same external variables (their state).
For example, see in JavaScript:
x = (function (){
var a ; a = 0;
return [ ()=>{ a+=1; return a}, ()=>{ a+=10; return a} ]
})();
x[0]() // "1"
x[1]() // "11"
x[0]() // "12"
x[0]() // "13"
x[1]() // "23"
Gforth "closures" does not provide this functionality.
Ruvim schrieb am Montag, 10. April 2023 um 15:37:50 UTC+2:
Closures that are created in the same lexical environment, share the
same external variables (their state).
For example, see in JavaScript:
x = (function (){
var a ; a = 0;
return [ ()=>{ a+=1; return a}, ()=>{ a+=10; return a} ]
})();
x[0]() // "1"
x[1]() // "11"
x[0]() // "12"
x[0]() // "13"
x[1]() // "23"
Gforth "closures" does not provide this functionality. Because actually
they are partially applied functions (partially applied definitions, if
you like), which are defined in special syntax.
"Sharing" external free variables, or getting an independent copy of them >(the lexical environment) at time of closure creation means a huge difference. >Each somewhat functional language seems to have cooked its own recipe
for closures, and IMO Javascript has overdone it here.
Ruvim <ruvim.pinka@gmail.com> writes:[...]
So a problem is how to create a partially applied function dynamically,
and how to free resources of an already unneeded function
It's pretty straightforward using "carnal knowledge", and as you
demonstrate, pretty messy if you want to use currently standard means;
[: ( -- addr )
0 <{: w^ a :}d a ;> drop {: a :}
here
a [{: a :}h 1 a +! a @ ;] ,
a [{: a :}h 10 a +! a @ ;] ,
;] execute constant x
minforth <minf...@arcor.de> writes:
Ruvim schrieb am Montag, 10. April 2023 um 15:37:50 UTC+2:
Closures that are created in the same lexical environment, share the
same external variables (their state).
For example, see in JavaScript:
x = (function (){
var a ; a = 0;
return [ ()=>{ a+=1; return a}, ()=>{ a+=10; return a} ]
})();
x[0]() // "1"
x[1]() // "11"
x[0]() // "12"
x[0]() // "13"
x[1]() // "23"
Gforth "closures" does not provide this functionality. Because actually
they are partially applied functions (partially applied definitions, if
you like), which are defined in special syntax.
"Sharing" external free variables, or getting an independent copy of them >(the lexical environment) at time of closure creation means a huge difference.This kind of sharing is an old concept already present in Algol 60,
Each somewhat functional language seems to have cooked its own recipe
for closures, and IMO Javascript has overdone it here.
exercised in the Man-or-Boy test (in the variables k and B) and in
Jensen's device; and of course you can use it in Scheme. It's not
needed in pure functional languages, because they don't change the
values of variables; so you can just replicate the value, no need to
worry about another user getting a stale value.
While (some) Scheme compilers use assignment conversion as a compiler technique, we decided to leave this job to the programmer in order to simplify the implementation of closures. I think that this is the
right choice:
* The need for assignment conversion rare; I think we have only used
it for proof-of-concept examples yet. So leaving it to the
programmer is not very burdensome.
* It makes the costs of things much more obvious. In the original
(Algol 60) man-or-boy program it's not obvious that k has this extra
cost, while in the Forth version it is quite easy to see.
Ruvim <ruvim.pinka@gmail.com> writes:
On 2023-04-07 11:51, Anton Ertl wrote:
What Gforth calls closures is intended
to be used for the purpose that closures (as well as curried and
partially-applied functions) are used for in Scheme; and if we have
[n:d + ;]
(what I call a pure-stack closure), it's not a partially-applied (or
partially-EXECUTEd?) colon definition, because no partial
application/execution has happened, and it's not a colon definition.
Partial application happens when a new xt is returned by this construct.
Yes. So the construct itself can be called a curried definition, or a definition with two-stage parameter passing.
The construct
[n:d + ;]
is actually a sugar over
[: + ;] partial1
Where "partial1 ( x1 xt1 -- xt )" performs partial application.
Except that we don't have PARTIAL1.
Closures that are created in the same lexical environment, share the
same external variables (their state).
For example, see in JavaScript:
x = (function (){
var a ; a = 0;
return [ ()=>{ a+=1; return a}, ()=>{ a+=10; return a} ]
})();
x[0]() // "1"
x[1]() // "11"
x[0]() // "12"
x[0]() // "13"
x[1]() // "23"
Gforth "closures" does not provide this functionality.
[: ( -- addr )
align here >r 0 ,
here
r@ [n:h 1 over +! @ ;] ,
r> [n:h 10 over +! @ ;] ,
;] execute constant x
x @ execute . \ 1
x cell+ @ execute . \ 11
x @ execute . \ 12
x @ execute . \ 13
x cell+ @ execute . \ 23
Here I used assignment conversion, which has been described by Dybvig
in the Scheme literature in 1987.
Each invocation of the quotation
produces a new instance of the variable in the dictionary with ",",
and the address of this variable is then passed to the two closures,
both of which then access the same variable. Section 5 of <http://www.euroforth.org/ef18/papers/ertl.pdf> describes the
application of assignment conversion to Forth.
The closures are
allocated on the heap here, to make the allocation of the array in the dictionary convenient.
The code above uses only stack stuff. A highly locals-oriented
version is:
[: ( -- addr )
0 <{: w^ a :}d a ;> drop {: a :}
here
a [{: a :}h 1 a +! a @ ;] ,
a [{: a :}h 10 a +! a @ ;] ,
;] execute constant x
x @ execute .
x cell+ @ execute .
x @ execute .
x @ execute .
x cell+ @ execute .
Here the <{: ... ;> syntax is used for producing home locations for
variables that you need to share, because they are changed. It
provides a common syntax for doing this for the different allocation
methods; of coures for each allocation method you can do it with
conventional means, but the syntax for the different allocation
methods is quite different.
Sysop: | Keyop |
---|---|
Location: | Huddersfield, West Yorkshire, UK |
Users: | 475 |
Nodes: | 16 (2 / 14) |
Uptime: | 18:38:17 |
Calls: | 9,487 |
Calls today: | 6 |
Files: | 13,617 |
Messages: | 6,121,092 |