3
\$\begingroup\$

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.)

asked Oct 7, 2015 at 7:09
\$\endgroup\$
1
  • \$\begingroup\$ The error (for/list ([x (in-nest-sequence 42 0)]) x) can be improved if a (procedure? func) test is inserted before make-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\$ Commented Oct 16, 2015 at 15:08

1 Answer 1

2
\$\begingroup\$

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 xs) 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

answered Oct 7, 2015 at 12:24
\$\endgroup\$
6
  • \$\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 a syntax-case fender/guard. Am I just checking whether (= (length (syntax->list #'(x ...))) (length (syntax->list #'(init ...)))) or is there a better way? \$\endgroup\$ Commented 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\$ Commented Oct 7, 2015 at 12:36
  • \$\begingroup\$ Turns out that (syntax-length stx) is in unstable/syntax. Hopefully it will be moved to racket/syntax at some point. \$\endgroup\$ Commented 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 provides syntax-length which simplifies the length-checking code a little. ;-) \$\endgroup\$ Commented Oct 7, 2015 at 12:47
  • \$\begingroup\$ to compare the lengths :-) \$\endgroup\$ Commented Oct 7, 2015 at 12:47

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.