Fascinating stuff Lutz, though I still think it seems a bit contrived when compared to using lexically scoped functions. Nonetheless, I set about to re-write the (delay) function for streams using your macro, and I did some basic benchmarks too.
Here is the new delay function (let me know if you see any ways of improving it btw):
Code: Select all
(define-macro (delay)
(let (memo-func-name (sym (string (args 0 0) "-memo")))
(if (nil? (sym memo-func-name memo-func-name nil))
(letex (a (args 0 0) b memo-func-name) (memoize a b)))
(letex (params (rest (args 0)))
(letex (f (cons memo-func-name 'params))
(fn () f)
)
)
)
)
Here's an example of its use:
Code: Select all
> (delay (+ 1 1))
(lambda () (+-memo 1 1))
I discovered that it's actually just a tad faster to use the first version of memoize, I'm guessing this is because it doesn't have to create strings out of the parameters. However, once you start calling the same function with many different parameters, I'm sure the second version will become faster.
Using this new delay function results in a significant reduction in performance on the first-call (to be expected), but in a significant speed-up if the exact same *exact* code is executed again (meaning even the parameters must be the same). I think that in most situations when an algorithm is run, it's probably only executed once with the same parameters. Therefore I would probably recommend against doing it in newLISP.
Again, on the topic of lexical vs. dynamic scoping, I think that this example clearly demonstrates some of the advantages of lexical scoping, which would most likely result in much faster times on the first run.
Here's an example of what I'm talking about (I'll post more detailed examples in the
steam thread):
Code: Select all
> (time (odd-fibs 50))
16
> (time (odd-fibs 30))
11
> (time (odd-fibs 30))
1
> (time (odd-fibs 25))
11