You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Also remove dependencies from utilities which is good thing
metatronic exports some functionality to help you write your own
more complex macros (and uses it internally)
Copy file name to clipboardExpand all lines: README.md
+211-8
Original file line number
Diff line number
Diff line change
@@ -395,7 +395,17 @@ I've always liked Scheme's named-`let` construct. It's pretty easy to provide a
395
395
396
396
Well, that's what it used to do: for a while I simply set the flag which controls whether it thinks an implementation supports tail-call elimination unilaterally to true, which means it will always create correct code, even if that code may cause stack overflows on implementations which don't eliminate tail calls[^6]. From 21st August 2021 the old code is now gone altogether (it is still available for inspection in old commits).
397
397
398
-
For a very long time I was confused about variable binding in `iterate`: I thought it was like `let*`, not like `let` although I'm not sure why. In the previous version of this code there was even an `iterate*` macro which claimed to be like `let*` and an `iterate` which claimed to be like `let`. But that was all just confusion, so `iterate*` is gone again.
398
+
From March 2023 there are now four variants on this macro which provide various options of evaluation order and stepping.
399
+
400
+
**`iterate`** is the original macro: this is just like named-`let` in Scheme. In particular it binds in parallel. So
401
+
402
+
```lisp
403
+
(let ((x 1))
404
+
(iterate n ((x 2) (y x))
405
+
(values x y)))
406
+
```
407
+
408
+
evaluates to `2` and `1`.
399
409
400
410
```lisp
401
411
(iterate foo ((x 1)
@@ -405,7 +415,7 @@ For a very long time I was confused about variable binding in `iterate`: I thoug
405
415
...)
406
416
```
407
417
408
-
turns into
418
+
simply turns into
409
419
410
420
```lisp
411
421
(labels ((foo (x y)
@@ -415,7 +425,150 @@ turns into
415
425
(foo 1 2))
416
426
```
417
427
418
-
Combined with `collecting`, `iterate` provides a surprisingly pleasant minimalist framework for walking over data structures in my experience.
428
+
**`iterate*`** is a variation of `iterate` which binds sequentially for the initial binding:
429
+
430
+
```lisp
431
+
(let ((x 1))
432
+
(iterate* n ((x 2) (y x))
433
+
(values x y)))
434
+
```
435
+
436
+
will evaluate to `2` and `2` (and the outer binding of `x` is now unused). Recursive calls are just function calls and evaluate their arguments as you would expect in both cases.
437
+
438
+
Additionally there are now two fancier macros: `iterating` and `iterating*`. Both of these support a variation of `do`-style stepping arguments: a binding like `(v init)` will bind `v` to `init` and then, by default, step it also to `init`. A binding like `(v init step)` will bind to `init` and then step it to `step`. Note that this is *not the same* as `do`: for `do``(v init)` will bind `v` to `init` and then to whatever its current value is. To achieve this with `iterating` you need to say `(v init v)`. See below for some rationale.
439
+
440
+
The local function now also takes keyword arguments with values which default to the stepping expressions. So:
441
+
442
+
```lisp
443
+
(iterating n ((n 0 (1+ n)))
444
+
(if (= n 10)
445
+
n
446
+
(n)))
447
+
```
448
+
449
+
Does what you think. But you can also provide arguments to the local function:
450
+
451
+
```lisp
452
+
(iterating n ((o t) (i 0 (1+ i)))
453
+
(print o)
454
+
(when (< i 10)
455
+
(n :o (if (evenp i) t nil))))
456
+
```
457
+
458
+
will print a succession of `t`s and `nil`s, for instance, `i` gets stepped automatically.
459
+
460
+
**`iterating`** is like `let` / `do`: all the binding that happens is in parallel. So in particular, as with `iterate`:
461
+
462
+
```lisp
463
+
(let ((x 1))
464
+
(iterating n ((x 2) (y x))
465
+
(values x y)))
466
+
```
467
+
468
+
evaluates to `2` and `1`. Similarly variable references in step forms are to the previous value of the variable:
469
+
470
+
```lisp
471
+
(iterating n ((i 2 (1+ i)) (j 1 i))
472
+
(when (< i 10)
473
+
(format t "~&~D ~D~%" i j)
474
+
(n)))
475
+
```
476
+
477
+
will print `2 1` then `3 2` and so on.
478
+
479
+
**`iterating*`** is like `let*` / `do*`: all the binding that happens is sequential. So in particular as with `iterate*`:
480
+
481
+
```lisp
482
+
(let ((x 1))
483
+
(iterating* n ((x 2) (y x))
484
+
(values x y)))
485
+
```
486
+
487
+
evaluates to `2`and `2` and the outer binding is unused. Also, the step forms now refer to the new values of variables to their left:
488
+
489
+
```lisp
490
+
(iterating* n ((i 2 (1+ i)) (j i i))
491
+
(when (< i 10)
492
+
(format t "~&~D ~D~%" i j)
493
+
(n)))
494
+
```
495
+
496
+
prints `2 2`, `3 3` and so on. This also applies to the optional arguments to the local function:
497
+
498
+
```lisp
499
+
> (iterating* n ((i 2 (1+ i)) (j i i))
500
+
(when (< i 10)
501
+
(format t "~&~D ~D~%" i j)
502
+
(if (evenp i)
503
+
(n (+ i 3))
504
+
(n :i (+ i 1)))))
505
+
2 2
506
+
5 5
507
+
6 6
508
+
9 9
509
+
```
510
+
511
+
Combined with `collecting`, `iterate` provides a surprisingly pleasant minimalist framework for walking over data structures in my experience, and i have use it extensively. `iterate*` , `iterating` and `iterating*` are much newer and may be slightly experimental.
512
+
513
+
### An example of `iterating`
514
+
Here is a simple sieve of Eratosthones:
515
+
516
+
```lisp
517
+
(defun sieve (n)
518
+
(declare (type (integer 2) n))
519
+
(let ((l (isqrt n))
520
+
(a (make-array (+ n 1) :element-type 'bit :initial-element 1)))
521
+
(declare (type (integer 1) l)
522
+
(type simple-bit-vector a))
523
+
(iterating* next ((c 2 (1+ c))
524
+
(marking (<= c l))
525
+
(primes '() primes))
526
+
(if (<= c n)
527
+
(if (zerop (bit a c))
528
+
;; not a prime
529
+
(next)
530
+
;; A prime
531
+
(if marking
532
+
(do ((m (* c c) (+ m c)))
533
+
((> m n) (next :primes (cons c primes)))
534
+
(setf (bit a m) 0))
535
+
(next :primes (cons c primes))))
536
+
(nreverse primes)))))
537
+
```
538
+
539
+
### Notes
540
+
The init and step forms for `iterating` and `iterating*` have different semantics than for `do` and `do*`, but the same semantics as for `doing` from my `simple-loops` hack. I am not sure that this is better -- simply being different is a bad thing -- but they work they way they do because I've ended up writing too many things which look like
541
+
542
+
```lisp
543
+
(do ((var <huge form> <same huge form>))
544
+
(...)
545
+
...)
546
+
```
547
+
548
+
which using `iterating` would be
549
+
550
+
```lisp
551
+
(iterating next ((var <huge form>))
552
+
...
553
+
(next))
554
+
```
555
+
556
+
This is one of the reasons that `iterating` is slightly experimental: I am not sure it's right and I won't know until I have used it more.
557
+
558
+
`iterating` and `iterating*` need to allow keyword arguments for what appears to be the local recursive function. Keyword argument parsing is clearly slightly hairy in general, so what they do is expand to something like this (this is the expansion for `iterating`:
559
+
560
+
```lisp
561
+
(labels ((#:n (x)
562
+
(flet ((n (&key ((:x #:x) 2))
563
+
(#:n #:x)))
564
+
(declare (inline n))
565
+
...)))
566
+
(#:n 2))
567
+
```
568
+
569
+
where all the gensyms that look the same are the same. Both functions could have had the same name of course, but that seemed gratuitous, especially for anyone reading the macroexpansion. The hope is that by using the little ancillary function, which is inlined, the keyword argument parsing will be faster. However `iterating` & `iterating*` are meant to be expressive at the cost of perhaps being slow in some cases.
570
+
571
+
`iterating` & `iterating*` went through a brief larval stage where they used optional arguments, but keyword arguments are so much more compelling we decided the possible performance cost was worth paying.
419
572
420
573
### Package, module
421
574
`iterate` lives in `org.tfeb.hax.iterate` and provides `:org.tfeb.hax.iterate`.
@@ -1736,6 +1889,8 @@ where, in this case, all the `#:<in>` symbols are the same symbol.
1736
1889
1737
1890
**`*default-metatronize-symbol-rewriter*`** is bound to the default symbol rewriter used by `metatronize`. Changing it will change the behaviour of `metatronize` and therefore of `defmacro/m` and `macrolet/m`. Reloading `metatronic` will reset it if you break things.
1738
1891
1892
+
**`rewrite-sources`** and **`rewrite-targets`** return a list of sources and targets from the rewrite table returned by `metatronize`.
1893
+
1739
1894
### Notes
1740
1895
Macros written with `defmacro/m` and `macrolet/m` in fact metatronize symbols *twice*: once when the macro is defined, and then again when it is expanded, using lists of rewritten & unique symbols from the first metatronization to drive a `rewriter` function. This ensures that each expansion has a unique set of gensymized symbols: with the above definition of `with-file-lines`, then
1741
1896
@@ -1753,10 +1908,56 @@ One consequence of this double-metatronization is that you should not use metatr
1753
1908
1754
1909
`metatronize` is *not a code walker*: it just blindly replaces some symbols with gensymized versions of them. Metatronic macros are typically easier to make more hygienic than they would otherwise be but they are very far from being hygienic macros.
1755
1910
1756
-
The tables used by`metatronize` are currently alists, which will limit its performance on vast structure. They may not always be, but they probably will be since macro definitions are not usually vast. Do not rely on them being alists.
1911
+
The tables used by`metatronize` are currently alists, which will limit its performance on vast structure. They may not always be, but they probably will be since macro definitions are not usually vast. Do not rely on them being alists, and in particular use `rewrite-sources` and `rewrite-targets` on the rewrite table.
1757
1912
1758
1913
`metatronize` does deal with sharing and circularity in list structure properly (but only in list structure). Objects which are not lists and not metatronic symbols are not copied of course, so if they were previously the same they still will be in the copy.
1759
1914
1915
+
### Writing more complicated macros
1916
+
All `defmacro/m` does is use `metatronize` to walk over the forms in the body of the macro, rewriting symbols appropriately. The expansion of the `with-file-lines` macro above is
where `m2` is an internal function which knows how to rewrite specific symbols: this is done so that even if you look at the expansion you can't extract the gensyms.
1931
+
1932
+
This is fine for macros like that, but there's a common style of macro which looks like this:
1933
+
1934
+
```lisp
1935
+
(defmacro iterate (name bindings &body body)
1936
+
(expand-iterate name bindings body nil))
1937
+
```
1938
+
1939
+
Where`expand-iterate` is probably some function which expands variations on `iterate`[^19]. Well, the answer is that it's complicated. But where, in a function like `expand-iterate`, you have code like:
1940
+
1941
+
```lisp
1942
+
(let ((secret-name (make-symbol ...)))
1943
+
...
1944
+
`(labels ((,secret-name ...))
1945
+
(,secret-name ...)))
1946
+
```
1947
+
1948
+
You can replace this with
1949
+
1950
+
```lisp
1951
+
(values
1952
+
(metatronize
1953
+
`(labels ((<secret-name> ...))
1954
+
(<secret-name> ...))))
1955
+
```
1956
+
1957
+
for instance. Here `values` is just suppressing the other values from `metatronize` which you don't need in this case.
1958
+
1959
+
However in general metatronic macros are far more useful for simple macros where there is no complicated expander function like this: that's what it was intended for.
1960
+
1760
1961
### Package, module
1761
1962
`metatronic` lives in and provides `:org.tfeb.hax.metatronic`.
1762
1963
@@ -1904,7 +2105,7 @@ If you want to have fine-grained control over log entry formats then two possibl
1904
2105
- you could make the value of `*log-entry-formatter*` be a generic function which can then dispatch on its second argument to return an appropriate log format;
1905
2106
- and/or you could define methods on `slog-to` for suitable `log-entry` subclasses which can select entry formats appropriately.
1906
2107
1907
-
The second approach allows you, for instance, to select locale-specific formats by passing keyword arguments to specify non-default locales to `slog-to`, rather than just relying on its class alone[^19].
2108
+
The second approach allows you, for instance, to select locale-specific formats by passing keyword arguments to specify non-default locales to `slog-to`, rather than just relying on its class alone[^20].
1908
2109
1909
2110
### The `logging` macro
1910
2111
**`(logging ([(typespec destination ...) ...]) form ...)`** establishes dynamic handlers for log entries which will log to the values of the specified destinations. Each `typespec` is as for `handler-bind`, except that the type `t` is rewritten as `log-entry`, which makes things easier to write. Any type mentioned in `typespec` must be a subtype of `log-entry`. The value of each destination is then found, with special handling for pathnames (see below) and these values are used as the destinations for calls to `slog-to`. As an example the expansion of the following form:
@@ -1969,7 +2170,7 @@ One important reason for this behaviour of `logging` is to deal with this proble
1969
2170
1970
2171
Because the absolute pathname of `foo.log`is computed and stored at the point of the logging macro you won't end up logging to multiple files: all the log entries will go to whatever the canonical version of `foo.log` was at the point that `logging` appeared.
1971
2172
1972
-
Finally note that calls to `slog` are completely legal but will do nothing outside the dynamic extent of a `logging` macro[^20], but `slog-to` will work quite happily and will write log entries. This includes when given pathname arguments: it is perfectly legal to write code which just calls `(slog-to "/my/file.log" "a message")`. See below on file handling.
2173
+
Finally note that calls to `slog` are completely legal but will do nothing outside the dynamic extent of a `logging` macro[^21], but `slog-to` will work quite happily and will write log entries. This includes when given pathname arguments: it is perfectly legal to write code which just calls `(slog-to "/my/file.log" "a message")`. See below on file handling.
1973
2174
1974
2175
Note that destinations which correspond to files (pathnames and strings) are opened by `logging`, with any needed directories being created and so on. This means that if those files *can't* be opened then you'll get an error immediately, rather than on the first call to `slog`.
1975
2176
@@ -2211,6 +2412,8 @@ The TFEB.ORG Lisp hax are copyright 1989-2022 Tim Bradshaw. See `LICENSE` for t
2211
2412
2212
2413
[^18]: So `(apply #'eq (metatronize '(<x> <x>)))` is true but `(apply #'eq (metatronize '(<> <>)))` is false.
2213
2414
2214
-
[^19]: An interim version of `slog` had a generic function, `log-entry-formatter` which was involved in this process with the aim of being able to select formats more flexibly, but it did not in fact add any useful flexibility.
2415
+
[^19]: This is in fact how `iterate` and `iterate*` work now, with `iterating` and `iterating*` sharing another expansion function.
2416
+
2417
+
[^20]: An interim version of `slog` had a generic function, `log-entry-formatter` which was involved in this process with the aim of being able to select formats more flexibly, but it did not in fact add any useful flexibility.
2215
2418
2216
-
[^20]: Well: you could write your own `handler-bind` / `handler-case` forms, but don't do that.
2419
+
[^21]: Well: you could write your own `handler-bind` / `handler-case` forms, but don't do that.
0 commit comments