Can I write a constructor for a Scheme (Racket) that accepts a variable number of arguments?

I understand how to write a function that takes an arbitrary number of arguments using dot notation. Example: (define (func-name . args) func-body) .

And I understand how to use constructor protection to preprocess the arguments of constructors, allowing me to pass different types to the constructor. Example:

 (struct struct-id (field-ids) #:guard (lambda (field-ids type-name) process-fields)) 

But it is as close as possible. Can you write a guard who takes an arbitrary number of arguments? Or is there another way to change what the constructor does?

+5
source share
2 answers

Just write a wrapper:

 (struct struct-id (abcd) #:constructor-name struct-id* #:guard (lambda (abcd type-name) do-stuff)) (define (struct-id (a) (b) (c) (d 'default-value)) (struct-id* abcd)) 

This gives you a constructor in which all field arguments are optional. Defining them in this way, rather than using dot notation, eliminates the need to parse the rest argument.

I have provided a default value for d , and Racket will do the default value of the remaining #f .

You can also define it to have keyword arguments:

 (define (struct-id #:a (a #f) #:b (b #f) #:cc #:d (d 'default)) (struct-id* abcd)) 

In the above case, #:c is a required argument because I settled on parentheses, I provided 'default as the 'default value of d , and the rest will have a default value of #f , which this time should be explicitly provided. Keywords can be passed to the constructor in any order.

If you use many structures, you might need a macro to define a wrapper for you:

 (begin-for-syntax (require (planet jphelps/loop)) ;; Installs a library on first use. Be patient. (define stx-symbol->string (compose symbol->string syntax->datum)) (define (make-constructor-name stx-name) (datum->syntax stx-name (string->symbol (string-append (stx-symbol->string stx-name) "*")))) (define (stx-symbol->stx-keyword stx-symbol) (datum->syntax stx-symbol (string->keyword (symbol->string (syntax->datum stx-symbol)))))) (define-syntax struct* (lambda (stx) (syntax-case stx () ((_ struct-name fields . options) #`(begin (struct struct-name fields . options) (define (#,(make-constructor-name #'struct-name) . #,(loop for name in (syntax-e #'fields) collect (stx-symbol->stx-keyword name) collect #`(#,name #f))) (struct-name . fields))))))) 

Then define your structures as follows:

 (struct* struct-id (abcd) #:guard whatever) 

You will automatically receive a constructor with keywords named struct-id* , which will not conflict with the names generated by the form struct .

EDIT

Obviously, the above macro, since it was originally written, did not work in module-based programs. I tested it only on REPL, which is more like Lisp, since you are allowed to override everything. This disguised the fact of adding the struct #:constructor-name option and an additional constructor name instead of overriding the name of an existing constructor. This is despite the fact that the #:extra-constructor-name option exists, which also creates an additional constructor name.

Fixing this problem so that it is completely seamless would require re-implementing the entire struct macro. You will have to rename the structure, and then create not only the constructor, but also all the accessors and mutators. An easier way would be to generate a constructor with a different name from the original constructor. I modified the code above to implement this workaround.

+3
source

Although you can specify a different name for the constructor with #:constructor-name , in my experience, that does not "release" the structure identifier that will be used as the name of the function:

 (struct baz (abc) #:transparent #:constructor-name -baz) (-baz 1 2 3) ;(baz 1 2 3) (define (baz abc) (-baz 1 2 3)) ; module: duplicate definition for identifier ; at: baz ; in: (define-values (baz) (new-lambda (abc) (-baz 1 2 3))) 

Therefore, I usually just define an alternative constructor using a different name and use it instead of the standard one (which is still available for use if necessary).


As for the macro for this, plus suggestions for args arguments and optional arguments: I could not get the macro in another answer to work for me.

  • The first problem is the one I mentioned above.
  • This is not the append* smoothing of arg specifications (#:kw id) in a function definition.
  • It builds the construct identifier instead of the one formed from the user structure identifier.
  • Finally, I prefer not to use the CL loop macro extracted from the old PLaneT package.

Instead, what works for me modifies and extends the code that I showed in the blog post as follows:

 #lang racket/base (require (for-syntax racket/base racket/list racket/syntax syntax/parse)) (begin-for-syntax (define syntax->keyword (compose1 string->keyword symbol->string syntax->datum))) (define-syntax (struct/kw stx) (define-syntax-class field (pattern id:id #:with ctor-arg #`(#,(syntax->keyword #'id) id)) (pattern [id:id default:expr] #:with ctor-arg #`(#,(syntax->keyword #'id) [id default]))) (syntax-parse stx [(_ struct-id:id (field:field ...) opt ...) (with-syntax ([ctor-id (format-id #'struct-id "~a/kw" #'struct-id)] [((ctor-arg ...) ...) #'(field.ctor-arg ...)]) ;ie append* #'(begin (struct struct-id (field.id ...) opt ...) (define (ctor-id ctor-arg ... ...) ;ie append* (struct-id field.id ...))))])) 

Usage example:

 ;; Define a struct type (struct/kw foo (ab [c #f]) #:transparent) ;; Use normal ctor (foo 1 2 3) ; => (foo 1 2 3) ;; Use keyword ctor (foo/kw #:a 1 #:b 2 #:c 3) ; => (foo 1 2 3) ;; Use keyword ctor, taking advantage of default arg for #:c field (foo/kw #:a 1 #:b 2) ; => (foo 1 2 #f) 

Assuming this is simplified, additional work may be required to support the entire normal struct .

+3
source

All Articles