in-nest-sequence
is a sequence generator that takes a function and an initial value, and the return value of invoking the function on the current value is used as the subsequent value. For example, (in-nest-sequence add1 0)
returns the sequence (0 1 2 3 4 ...)
.
Recently, soegaard challenged me to write a define-sequence-syntax
version of in-nest-sequence
, that is, a macro-based version. I decided to give it a try:
#lang racket
(require (for-syntax unstable/syntax))
(provide (rename-out [*in-nest-sequence in-nest-sequence]))
(define in-nest-sequence
(case-lambda
[(func init)
(make-do-sequence
(thunk (values identity func init #f #f #f)))]
[(func . inits)
(make-do-sequence
(thunk (values (curry apply values)
(lambda (args)
(call-with-values (thunk (apply func args)) list))
inits #f #f #f)))]))
(define-sequence-syntax *in-nest-sequence
(lambda () #'in-nest-sequence)
(lambda (stx)
(syntax-case stx ()
[[(x ...) (_ func init ...)]
(unless (= (syntax-length #'(x ...)) (syntax-length #'(init ...)))
(raise-syntax-error 'in-nest-sequence
(format "~a values required" (syntax-length #'(x ...)))
stx #'(init ...)))
(with-syntax ([for-arity (syntax-length #'(init ...))]
[(value ...) (generate-temporaries #'(init ...))]
[(y ...) (generate-temporaries #'(init ...))])
#'[(x ...) (:do-in ([(f) func])
(unless (procedure-arity-includes? f for-arity)
(raise-arity-error f (procedure-arity f) init ...))
([value init] ...)
#t
([(x ...) (values value ...)]
[(y ...) (f value ...)])
#t
#t
(y ...))])])))
I'm a complete noob at using define-sequence-syntax
, so I appreciate any and all style, performance, and/or general feedback about my code. (See the original post for some usage examples.)
1 Answer 1
For cases where the the user follows the syntax and the function returns and receives the correct number of values, this works fine.
It is a good principle to catch errors as early as possible.
Consider this example:
(in-iterate add1)
Here the user supplies a function add1
that has arity 1, but supplies no arguments. This will eventually lead to an "result arity mismatch;" error, when add1
. This error can be caught earlier in using procedure-arity
to check that enough init values are supplied. The error message presented to the user can be "expected 1 initial value, but received 0".
In the *in-iterate
consider: [(x ...) (_ func init ...)]
.
Here the number of bound identifiers (the x
s) must be the same as the number of init expressions. If they are not, a raise-syntax-error
with stx
as the source of the error can be used.
An example that should be a syntax-error (and not a runtime error):
(define (add1/2 x y) (values (+ x 1) (+ y 2)))
(for/list ([n 10]
[(x y) (in-iterate add1/2 0)])
(list x y))
The name of the construct is a bit off. In in-list
and in-sequence
we have the pattern in-noun
, so that suggest in-iteration
might be better. However iteration
is a bit vague. Maybe one in-nested-sequence
or in-nest-sequence
based on the name used in Mathematica? http://reference.wolfram.com/language/ref/NestList.html
-
\$\begingroup\$ Wow, that's awesome feedback. Thanks! For checking that
(x ...)
and(init ...)
are the same length, I guess one possible solution is to use asyntax-case
fender/guard. Am I just checking whether(= (length (syntax->list #'(x ...))) (length (syntax->list #'(init ...))))
or is there a better way? \$\endgroup\$C. K. Young– C. K. Young2015年10月07日 12:27:09 +00:00Commented Oct 7, 2015 at 12:27 -
\$\begingroup\$ I would use the same expression to the lengths. You could add a fender - and then add a new case to handle the error. When reading code I have a tendency to overlook fenders (and thus make mistakes). So I might be tempted to add the error checking directly to the existing clause - but that's just a matter of personal preference. \$\endgroup\$soegaard– soegaard2015年10月07日 12:36:13 +00:00Commented Oct 7, 2015 at 12:36
-
\$\begingroup\$ Turns out that
(syntax-length stx)
is inunstable/syntax
. Hopefully it will be moved toracket/syntax
at some point. \$\endgroup\$soegaard– soegaard2015年10月07日 12:46:54 +00:00Commented Oct 7, 2015 at 12:46 -
\$\begingroup\$ I don't know how to parse "I would use the same expression to the lengths", sorry. :-( BTW, I feel horrible about using
unstable/syntax
(which you just mentioned, lol), but apparently it providessyntax-length
which simplifies the length-checking code a little. ;-) \$\endgroup\$C. K. Young– C. K. Young2015年10月07日 12:47:18 +00:00Commented Oct 7, 2015 at 12:47 -
\$\begingroup\$ to compare the lengths :-) \$\endgroup\$soegaard– soegaard2015年10月07日 12:47:34 +00:00Commented Oct 7, 2015 at 12:47
(for/list ([x (in-nest-sequence 42 0)]) x)
can be improved if a(procedure? func)
test is inserted beforemake-do-sequence
is called. Also: I am a bit torn about removing the one-value case. I am not 100% sure the optimizer can produce code that works equally well from the multiple values case. \$\endgroup\$