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

\$\endgroup\$
  • \$\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\$ – soegaard Oct 16 '15 at 15:08
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

\$\endgroup\$
  • \$\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\$ – Chris Jester-Young Oct 7 '15 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 Oct 7 '15 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\$ – soegaard Oct 7 '15 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\$ – Chris Jester-Young Oct 7 '15 at 12:47
  • \$\begingroup\$ to compare the lengths :-) \$\endgroup\$ – soegaard Oct 7 '15 at 12:47

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.