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" '()))
)
有两类策略可以修复"属性值为假"错误:
-
放置默认值或替代项,以便属性永远不会为 false。
(a) 将
~optional
与#:defaults
一起使用(b) 使用
~or
,可能与~parse
(c) 使用具有多种模式的语法类
-
处理正文中的属性为假。
(a) 使用
unsyntax
和if
(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-splicing
和if
,因此它是 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!
另一种方法是将省略号用法移动到语法类,如以下问题所示:与可选模式匹配并绑定属性的拼接语法类