我可以为一个带有可变数量参数的Scheme(Racket)结构编写构造函数吗?

时间:2015-07-03 18:02:35

标签: struct constructor scheme racket variadic-functions

我理解如何编写一个使用点表示法获取任意数量参数的函数。示例:(define (func-name . args) func-body)

我理解如何使用构造函数guard来预处理构造函数参数,允许我将不同的类型传递给构造函数。例如:

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

但是我尽可能接近。你能写一个带有任意数量参数的守卫吗?或者是否有其他方法来修改结构构造函数的作用?

2 个答案:

答案 0 :(得分:3)

只需写一个包装器:

(struct struct-id (a b c d) #:constructor-name struct-id*
      #:guard (lambda (a b c d type-name) do-stuff))

(define (struct-id (a) (b) (c) (d 'default-value))
        (struct-id* a b c d))

它为您提供了一个构造函数,其中所有字段参数都是可选的。用这种方式定义它们而不是用点符号来节省你不必解析rest-argument。

我提供了d的默认值,而Racket将设置其他#f的默认值。

您还可以将其定义为具有关键字参数:

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

在上面的情况中,#:c是必需的参数,因为我没有使用括号,我提供了'default作为默认值d,其他的将具有默认值#f的{​​{1}},这次必须明确提供。关键字可以按任何顺序传递给构造函数。

如果你使用了很多结构,你可能需要一个宏来为你定义包装器:

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

然后像这样定义你的结构:

(struct* struct-id (a b c d) #:guard whatever)

您将自动获得一个名为struct-id*的基于关键字的构造函数,该构造函数不会与struct表单生成的名称冲突。

修改

显然,最初编写的上述宏在基于模块的程序中并不起作用。我只在REPL上测试它,它更像是一个Lisp,因为你可以重新定义它。这掩盖了struct #:constructor-name选项添加和其他构造函数名称而不是覆盖现有构造函数名称的事实。尽管有一个#:extra-constructor-name选项也可以创建一个额外的构造函数名称。

以完全无缝的方式解决此问题需要您重新实现整个struct宏。您必须重命名结构,然后不仅生成构造函数,还生成所有访问器和更改器。一个更简单的解决方法是生成一个与原始构造函数名称不同的构造函数。我已经编辑了上面的代码来实现这个解决方法。

答案 1 :(得分:3)

虽然您可以使用#:constructor-name为构造函数指定另一个名称,但根据我的经验,并没有"释放"要用作函数名称的结构标识符:

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

所以我通常只使用另一个名称定义一个替代构造函数,并使用它而不是默认名称(如果需要,仍然可以使用)。

至于执行此操作的宏,加上提供关键字args和可选参数:我无法让其他答案中的宏为我工作。

  • 第一个问题是我上面提到的问题。
  • 函数定义中的append* arg规范并没有(#:kw id) - 扁平化。
  • 它将标识符construct硬连接,而不是由用户的结构标识符形成的标识符。
  • 最后,我不想使用旧的PLaneT软件包中的CL loop宏。

相反,对我来说有用的是修改和扩展我在a blog post中显示的代码,如下所示:

#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 ...)]) ;i.e. append*
       #'(begin
           (struct struct-id (field.id ...) opt ...)
           (define (ctor-id ctor-arg ... ...) ;i.e. append*
             (struct-id field.id ...))))]))

使用示例:

;; Define a struct type
(struct/kw foo (a b [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)

当然这很简单,需要更多工作来支持正常struct可以做的所有事情。