[email protected] (Anton Ertl) writes:
[email protected] (Ahmed) writes:
Hello,
In the thread: >>https://www.novabbs.com/devel/article-flat.php?group=comp.lang.forth&id=16944&first=1&last=25#start
Paul Robin posted:
For example, sin (sine) is a function, whose input is a real and
whose
output is another real. And "derivative" (d/dx) is a function whose
input is a function, and whose output is another function. So you
get
that d/dx (sin) = cos, or some numerical approximation of same. In
Python or Haskell, this is easy, e.g.:
h = 0.0001
d_over_dx f = g where { g x = (f(x+h) - f(x)) / h }
dsin = d_over_dx sin
print (dsin 0.5) -- prints 0.8775585891507287
print (cos 0.5) -- prints 0.8775825618903728
Ahmed Melahi's solution (using a better formula):
: d/dx ( xt -- )
create ,
does> @ dup ( f: x -- y) fdup 1e-4 f+ execute fswap 1e-4 f-
execute f- 0.5e4 f* ;
Usage example:
' fsin d/dx fdsin
My solution (using Paul Rubin's formula):
0.0001e fvalue h
: d/dx1 ( r1 xt -- r2 )
fdup h f+ dup execute fswap execute f- h f/ ;
: d/dx ( xt1 -- xt2 )
r :noname ( r1 -- r2 ) r> postpone literal postpone d/dx1 postpone ; ;
0.5e ' fsin d/dx execute f. \ prints 0.877558589150729
Another variant is to compile xt1 into xt2; here a standard version
(with Paul Rubin's formula):
0.0001e fvalue h
: d/dx ( xt1 -- xt2 )
>r :noname ( r1 -- r2 )
postpone fdup postpone h postpone f+ r@ compile,
postpone fswap r> compile, postpone f- postpone h postpone f/
postpone ; ;
0.5e ' fsin d/dx execute f. \ prints "0.877558589150729"
If I do
0.5e ' fsin d/dx disasm/f
on VFX64, I get:
( 0050A358 D9C0 ) FLD ST
( 0050A35A DB2DF0FEFFFF ) FLD TBYTE FFFFFEF0 [RIP] @0050A250 ( 0050A360 DEC1 ) FADDP ST(1), ST
( 0050A362 E8F9CBFFFF ) CALL 00506F60 FSIN
( 0050A367 D9C9 ) FXCH ST(1)
( 0050A369 E8F2CBFFFF ) CALL 00506F60 FSIN
( 0050A36E DEE9 ) FSUBP ST(1), ST
( 0050A370 DB2DDAFEFFFF ) FLD TBYTE FFFFFEDA [RIP] @0050A250 ( 0050A376 DEF9 ) FDIVP ST(1), ST
( 0050A378 C3 ) RET/NEXT
( 33 bytes, 10 instructions )
Now on to the less portable stuff. The many POSTPONE's in the code
above hamper readability. So let's use ]] ... [[ to get rid of them:
: d/dx ( xt1 -- xt2 )
>r :noname ( r1 -- r2 ) ]]
fdup h f+ [[ r@ compile, ]] fswap [[ r> compile, ]] f- h f/ ;
[[ ;
This example may also make it clear why the brackets are oriented the
way they are. Compiles on Gforth and VFX64 (and to the same code as
above on VFX64).
Gforth offers an additional nicety. The following code generates the
same code as the previous D/DX.
: d/dx {: xt: xt1 -- xt2 :}
:noname ( r1 -- r2 ) ]] fdup h f+ xt1 fswap xt1 f- h f/ ; [[ ;
The XT: results in XT1 being a defer-flavoured local, so mentioning
the name executes the xt, and POSTPONEing it (e.g., within ]]..[[)
COMPILE,s it.
Another variant is to use Gforth's flat closures:
: d/dx ( xt1 -- xt2 )
[{: xt: xt1 :}d fdup h f+ xt1 fswap xt1 f- h f/ ;] ;
The functionality is the same as my previous D/DX implementations, and
the source code looks similar to the previous one, but the
implementation is quite different:
' fsin d/dx
uses 3 cells in the dictionary (and none in the native code) with the flat-closure version, while the three definitions before that (which
use POSTPONE in some form or other) take 16 cells of dictionary and
148 bytes of native code. Generating a closure is also much faster.
However, the POSTPONE-based variants will run faster.
Stack purists will dislike the local in the closure. Gforth also
provides a pure-stack variant, and using it here will look as follows:
: d/dx ( xt1 -- xt2 )
[n:d fdup h f+ dup execute fswap execute f- h f/ ;] ;
This variant makes the difference to the POSTPONE-based approaches
more obvious by calling EXECUTE explicitly.
Ahmed Melahis CREATE...DOES> variant can be seen as the classical
Forth syntax for the pure-stack flat-closure approach; he uses a
different formula and inlines H, which obscures this a bit, but using
the same formula and H, a CREATE-DOES variant looks as follows:
: d/dx-named ( xt1 "name" -- xt2 )
create ,
does> ( r1 -- r2 )
@ fdup h f+ dup execute fswap execute f- h f/ ;
where the code from FDUP to F/ is the same as in the pure-stack
closure version above. If you use D/DX-NAMED with Gforth's NONAME,
calling
' fsin noname d/dx-named
consumes three cells, like the flat-closure variants.
Some may conclude that this shows that flat closures are unnecessary
(and some may extend this to other newer features like defer-flavoured
locals and ]]...[[). However, note that flat closures can not just be allocated in the dictionary, but also on the locals stack or on the
heap, unlike CREATE..DOES>.
Of course, if we judge expressive power by Turing completeness, one
can reject all new features (or leave away many existing ones),
because they do not change Turing completeness, but there is a reason
why we implement more than just a minimal Turing-complete language, so
we obviously use other criteria for judging the desirability of
features.
- anton
--
M. Anton Ertl
http://www.complang.tuwien.ac.at/anton/home.html
comp.lang.forth FAQs:
http://www.complang.tuwien.ac.at/forth/faq/toc.html
New standard:
https://forth-standard.org/
EuroForth 2023:
https://euro.theforth.net/2023
--- SoupGate-Win32 v1.05
* Origin: fsxNet Usenet Gateway (21:1/5)