如何在包含省略号的〜Optional中访问语法类属性?

Jér*_*tin 10 racket

TL; DR

我试图扩大可选省略号(~optional datum:my-class ...)成一种形式,使用从属性,my-class像这样:#'(list datum.attr ...)

但是由于表单是可选的,因此当表单不存在时,它将解析为#f,省略号模式不喜欢它:

;; ?: attribute contains non-list value
;;   value: #f
Run Code Online (Sandbox Code Playgroud)

我尝试使用的#:defaults参数~optional,如下所示:

(~optional datum:my-class ... #:defaults ([(datum 1) null]))
Run Code Online (Sandbox Code Playgroud)
;; which works for usage:
#'(list datum ...)
;; but not for:
#'(list datum.attr ...)
Run Code Online (Sandbox Code Playgroud)

我通过使用以下技巧使它起作用:

(~optional datum:my-class ...)

#'(list #,@(if (attribute datum) #'(datum.attr ...) #'()))
Run Code Online (Sandbox Code Playgroud)

我正在尝试寻找是否有更好的方法。

完整的可运行示例

我尝试将其保持在最低限度。检出测试子模块。要查看此问题,请取消注释parse-bag和运行的四个实现之一raco test file.rkt

#lang racket/base

(provide parse-bag)

(require
  (for-syntax racket/base syntax/parse)
  syntax/parse)

(struct bag (label objects) #:transparent)
(struct object (name) #:transparent)

(begin-for-syntax
  (define-syntax-class obj-exp
    #:datum-literals (object name)
    (pattern (object (name <name>:str))
             #:with result #'(object <name>))))

;; IMPLEMENTATION ONE
;; DESC: The naive but failing approach
;; UNCOMMENT TO TEST
#;(define-syntax (parse-bag stx)
  (syntax-parse stx
      #:datum-literals (label objects)
      [(_ (label <label>:str)
          (~optional (objects <object>:obj-exp ...)))
       #'(bag <label>
              (list <object>.result ...))]))
;; ?: attribute contains non-list value
;;   value: #f


;; IMPLEMENTATION TWO
;; DESC: adding defaults will fail too
;; UNCOMMENT TO TEST
#;(define-syntax (parse-bag stx)
  (syntax-parse stx
      #:datum-literals (label objects)
      [(_ (label <label>:str)
          (~optional (objects <object>:obj-exp ...)
                     #:defaults ([(<object> 1) null]))) ; (HERE)
       #'(bag <label>
              (list <object>.result ...))]))
;; ?: attribute contains non-list value
;;   value: #f


;; IMPLEMENTATION THREE
;; DESC: it won't fail when not using syntax-class attributes
;; UNCOMMENT TO TEST
#;(define-syntax (parse-bag stx)
  (syntax-parse stx
      #:datum-literals (label objects)
      [(_ (label <label>:str)
          (~optional (objects <object>:obj-exp ...)
                     #:defaults ([(<object> 1) null])))
       #'(bag <label>
              (list <object> ...))])) ; (HERE)
;; name: unbound identifier


;; IMPLEMENTATION FOUR
;; DESC: it works, but I find it ugly
;; UNCOMMENT TO TEST
#;(define-syntax (parse-bag stx)
  (syntax-parse stx
      #:datum-literals (label objects)
      [(_ (label <label>:str)
          (~optional (objects <object>:obj-exp ...)))
       #`(bag <label>
              (list #,@(if (attribute <object>)
                           #'(<object>.result ...)
                           #'())))]))


(module+ test (require rackunit)

(check-equal?
  (parse-bag
    (label "Biscuits")
    (objects (object (name "Cookie"))
             (object (name "Brownie"))))

  (bag "Biscuits"
        (list (object "Cookie")
              (object "Brownie"))))

(check-equal?
  (parse-bag (label "Sweets"))
  (bag "Sweets" '()))

)
Run Code Online (Sandbox Code Playgroud)

Ale*_*uth 10

有两类策略可解决“属性值为假”错误:

  1. 放置默认值或替代值,以使该属性永远不会为假。

    (a)~optional#:defaults

    (b)~or与一起使用~parse

    (c)使用具有多个模式的语法类

  2. 处理体内的错误属性。

    (a)使用unsyntaxif

    (b)使用 ~?

1(一)

问题中的实现2和3以及Jérôme答案中的代码都~optional与一起使用#:defaults,因此它们都试图用1(a)对其进行修复。

杰罗姆(Jérôme)的答案显示了对此的正确使用。要点是,如果您使用的属性<object>.result,则需要指定默认值<object>.result,而不仅仅是<object>

但是,如果obj-exp您需要在类中使用多个属性,则有一个缺点。这将要求您为每个指定一个默认值:

(~optional (objects <object>:obj-exp ...)
           #:defaults ([(<object>.result 1) null]
                       [(<object>.x 1) null]))
Run Code Online (Sandbox Code Playgroud)

1(b)

如果您使用的语法类的多种属性,如<object>.result<object>.x<object>.y<object>.z,等,然后如图1(a)将需要您单独指定为每一个默认值。为了避免这种情况,请不要编写以下代码:

(~optional (objects <object>:obj-exp ...)
           #:defaults ([(<object>.result 1) null]
                       [(<object>.x 1) null]
                       [(<object>.y 1) null]
                       [(<object>.z 1) null]))
Run Code Online (Sandbox Code Playgroud)

你可以用~or~parse这样的:

(~or (objects <object>:obj-exp ...)
     (~and (~seq) (~parse (<object>:obj-exp ...) null)))
Run Code Online (Sandbox Code Playgroud)

1(c)

(define-splicing-syntax-class maybe-objects
  #:datum-literals (objects)
  [pattern (objects <object>:obj-exp ...)]
  [pattern (~seq) #:with (<object>:obj-exp ...) null])
Run Code Online (Sandbox Code Playgroud)

2(a)和(b)

问题中的实现4使用unsyntax-splicingif,因此是2(a)的示例:

#,@(if (attribute <object>)
       #'(<object>.result ...)
       #'())
Run Code Online (Sandbox Code Playgroud)

但是,正如您指出的那样,这看起来很丑陋。而且它还有另一个问题。如果其本身在省略号下,则会分解,因为椭圆不会在#,或内携带其效果#,@

因此~?存在2(b)。除了#,@(if ....)可以使用,您还可以使用:

(~? (<object>.result ...) ())
Run Code Online (Sandbox Code Playgroud)

那不是很有效,但是它的这个变体可以做到:

(~? (list <object>.result ...) (list))
Run Code Online (Sandbox Code Playgroud)

在实现4的变体中使用它:

(define-syntax (parse-bag stx)
  (syntax-parse stx
    #:datum-literals (label objects)
    [(_ (label <label>:str)
        (~optional (objects <object>:obj-exp ...)))
     #`(bag <label>
            (~? (list <object>.result ...) (list)))]))
Run Code Online (Sandbox Code Playgroud)