如何访问包含省略号的"~optional"中的语法类属性?



TL;博士

我正在尝试将可选的省略号(~optional datum:my-class ...)扩展为使用my-class属性的形式,如下所示:#'(list datum.attr ...).

但是由于表单是可选的,当它不存在时,它会解析为#f,省略号图案不喜欢:

;; ?: attribute contains non-list value
;;   value: #f

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

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

我通过使用这个技巧让它工作:

(~optional datum:my-class ...)
#'(list #,@(if (attribute datum) #'(datum.attr ...) #'()))

我试图找到是否有更好的方法。

完整的可运行示例

我试图将其保持在最低限度。查看测试子模块。 要查看该问题,请取消注释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" '()))
)

有两类策略可以修复"属性值为假"错误:

  1. 放置默认值或替代项,以便属性永远不会为 false。

    (a) 将~optional#:defaults一起使用

    (b) 使用~or,可能与~parse

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

  2. 处理正文中的属性为假。

    (a) 使用unsyntaxif

    (b) 使用~?

1(a)

问题中的实现 2 和 3,以及 Zoé 答案中的代码,都使用~optional#:defaults,所以它们都是尝试用 1 (a) 修复它。

佐伊的回答表明了对这一点的正确用法。重点是,如果使用像<object>.result这样的属性,则需要为<object>.result指定默认值,而不仅仅是<object>

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

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

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

您可以使用~or~parse如下所示:

(~or (objects <object>:obj-exp ...)
(~and (~seq) (~parse (<object>:obj-exp ...) null)))

1(c)

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

2(a)和(b)

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

#,@(if (attribute <object>)
#'(<object>.result ...)
#'())

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

这就是为什么~?存在 2 (b) 的原因。#,@(if ....)您可以使用:

(~? (<object>.result ...) ())

这不太有效,但它的这个变体确实有效:

(~? (list <object>.result ...) (list))

在实现 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)))]))

使用#:defaults时,需要指定属性:

(~optional <object>:obj-exp ... #:defaults ([(<object>.result 1) null]))

完整代码:

(define-syntax (parse-bag stx)
(syntax-parse stx
#:datum-literals (label objects)
[(_ (label <label>:str)
(~optional (objects <object>:obj-exp ...)
#:defaults ([(<object>.result 1) null]))) ; + attribute here
#'(bag <label>
(list <object>.result ...))])) ; now it works!

另一种方法是将省略号用法移动到语法类,如以下问题所示:与可选模式匹配并绑定属性的拼接语法类

最新更新