Scheme Racket中函数参数的关键字和默认值的宏
关键字和默认参数可在Racket函数中使用,如本页所示: 由于Racket可以轻松创建新的语言定义,因此是否可以创建宏,以便按如下方式定义函数:Scheme Racket中函数参数的关键字和默认值的宏,scheme,racket,Scheme,Racket,关键字和默认参数可在Racket函数中使用,如本页所示: 由于Racket可以轻松创建新的语言定义,因此是否可以创建宏,以便按如下方式定义函数: (define (greet2 (hi "hello") (given "Joe") (surname "Smith")) (string-append hi ", " given " " surname)) (greet2 (surname "Watchman") (hi "hi") (given "Robert") ) 应该可以按如下任
(define (greet2 (hi "hello") (given "Joe") (surname "Smith"))
(string-append hi ", " given " " surname))
(greet2 (surname "Watchman") (hi "hi") (given "Robert") )
应该可以按如下任意顺序调用带有参数的函数:
(define (greet2 (hi "hello") (given "Joe") (surname "Smith"))
(string-append hi ", " given " " surname))
(greet2 (surname "Watchman") (hi "hi") (given "Robert") )
为了澄清,以下工作:
(define (greet3 #:hi [hi "hello"] #:given [given "Joe"] #:surname [surname "Smith"])
(string-append hi ", " given " " surname))
(greet3 #:surname "Watchman" #:hi "hey" #:given "Robert" )
但我希望下面的内容起作用(括号可以是()或[],甚至是{}):
基本上,我想去掉“#:姓氏”部分(因为它看起来是重复的),以提高打字的方便性
如何创建这样的宏?我尝试了以下代码行:
(define-syntax-rule (myfn (arg1 val1) (arg2 val2) ...)
(myfn #:arg1 val1 #:arg2 val2 ...))
但它不起作用
谢谢你的评论/回答
编辑:
我修改了@AlexKnauth的answer中的代码,使用{}而不是[],这也很有效:
(require syntax/parse/define ; for define-simple-macro
(only-in racket [define old-define] [#%app old-#%app])
(for-syntax syntax/stx)) ; for stx-map
(begin-for-syntax
;; identifier->keyword : Identifer -> (Syntaxof Keyword)
(define (identifier->keyword id)
(datum->syntax id (string->keyword (symbol->string (syntax-e id))) id id))
;; for use in define
(define-syntax-class arg-spec
[pattern name:id
;; a sequence of one thing
#:with (norm ...) #'(name)]
[pattern {name:id default-val:expr}
#:when (equal? #\{ (syntax-property this-syntax 'paren-shape))
#:with name-kw (identifier->keyword #'name)
;; a sequence of two things
#:with (norm ...) #'(name-kw {name default-val})]))
(define-simple-macro (define (fn arg:arg-spec ...) body ...+)
(old-define (fn arg.norm ... ...) body ...))
(begin-for-syntax
;; for use in #%app
(define-syntax-class arg
[pattern arg:expr
#:when (not (equal? #\{ (syntax-property this-syntax 'paren-shape)))
;; a sequence of one thing
#:with (norm ...) #'(arg)]
[pattern {name:id arg:expr}
#:when (equal? #\{ (syntax-property this-syntax 'paren-shape))
#:with name-kw (identifier->keyword #'name)
;; a sequence of two things
#:with (norm ...) #'(name-kw arg)]))
(require (for-syntax (only-in racket [#%app app])))
(define-simple-macro (#%app fn arg:arg ...)
#:fail-when (app equal? #\{ (app syntax-property this-syntax 'paren-shape))
"function applications can't use `{`"
(old-#%app fn arg.norm ... ...))
用法示例:
> (define (greet5 hi {given "Joe"} {surname "Smith"})
(string-append hi ", " given " " surname))
> (greet5 "Hey" {surname "Watchman"} {given "Robert"})
"Hey, Robert Watchman"
而且论点的顺序也很灵活:
> (greet5 {surname "Watchman"} "Howya" {given "Robert"})
"Howya, Robert Watchman"
现在,简单的define语句不起作用:
(define x 0)
define: bad syntax in: (define x 0)
相反,
(旧的define x 0)
起作用。您可以这样做,但您需要使用define simple macro
和identifier->keyword
辅助函数来完成稍微复杂的操作
你可以定义你自己的define
表单和你自己的#%app
用于函数应用程序,但要做到这一点,你需要扩展到racket的旧版本,因此你需要导入重命名的版本,只使用require表单中的
您还需要将identifier->keyword
函数映射到所有标识符上。一个有用的函数是syntax/stx
中的stx-map
。它类似于map
,但也适用于语法对象
#lang racket
(require syntax/parse/define ; for define-simple-macro
(only-in racket [define old-define] [#%app old-#%app])
(for-syntax syntax/stx)) ; for stx-map
要为宏定义用于转换语法的辅助函数,需要将其放入beginforsyntax
(begin-for-syntax
;; identifier->keyword : Identifer -> (Syntaxof Keyword)
(define (identifier->keyword id)
(datum->syntax id (string->keyword (symbol->string (syntax-e id))) id id)))
这个答案定义了两个版本:一个只支持命名参数,另一个同时支持命名参数和位置参数。但是,它们都将使用identifier->keyword
helper函数
刚刚命名的参数
新版本的define
使用identifier->keyword
帮助函数将arg名称
转换为关键字,但由于需要转换它们的语法列表,因此使用stx映射
然后,它将关键字与[arg name default val]
对组合在一起,以创建arg kw[arg name default val]
序列。对于具体的代码,这将把#:hi
与[hi“hello”]
分组,以创建#:hi[hi“hello”]
序列,这是旧的define表单所期望的
(define-simple-macro (define (fn [arg-name default-val] ...) body ...+)
;; stx-map is like map, but for syntax lists
#:with (arg-kw ...) (stx-map identifier->keyword #'(arg-name ...))
;; group the arg-kws and [arg-name default-val] pairs together as sequences
#:with ((arg-kw/arg+default ...) ...) #'((arg-kw [arg-name default-val]) ...)
;; expand to old-define
(old-define (fn arg-kw/arg+default ... ...) body ...))
这定义了一个#%app
宏,该宏将隐式插入所有函数应用程序中<代码>(f stuff…
将扩展为(#%app f stuff…
),因此(greet4[hi“hey”])
将扩展为(#%app greet4[hi“hey”])
此宏将(#%app greet4[hi“hey”])
转换为(old-#%app greet4::hi“hey”)
使用新的定义表单:
> (define (greet4 [hi "hello"] [given "Joe"] [surname "Smith"])
;; have to use old-#%app for this string-append call
(old-#%app string-append hi ", " given " " surname))
这些程序简单地使用了上面定义的新的#%app
宏:
> (greet4 [surname "Watchman"] [hi "hey"] [given "Robert"])
"hey, Robert Watchman"
省略参数将使用默认值:
> (greet4 [hi "hey"] [given "Robert"])
"hey, Robert Smith"
而像greet4
这样的函数仍然可以在高阶函数中使用:
> (old-define display-greeting (old-#%app compose displayln greet4))
> (display-greeting [hi "hey"] [given "Robert"])
hey, Robert Smith
命名参数和位置参数
上面的宏只支持命名参数,因此不能定义使用位置参数的函数。但是,可以在同一宏中同时支持位置参数和命名参数
为此,我们必须将方括号[
和]
设为“特殊”,以便定义和#%app
可以区分命名参数和表达式。为此,我们可以使用(语法属性stx'paren shape)
,如果stx
是用方括号写的,那么它将返回字符\[
(begin-for-syntax
;; for use in #%app
(define-syntax-class arg
[pattern arg:expr
#:when (not (equal? #\[ (syntax-property this-syntax 'paren-shape)))
;; a sequence of one thing
#:with (norm ...) #'(arg)]
[pattern [name:id arg:expr]
#:when (equal? #\[ (syntax-property this-syntax 'paren-shape))
#:with name-kw (identifier->keyword #'name)
;; a sequence of two things
#:with (norm ...) #'(name-kw arg)]))
因此,要在定义中指定位置参数,只需使用普通标识符,而要使用命名参数,则需要使用方括号。因此,参数规范可以是这些变量中的任何一个。您可以使用
由于宏使用它来转换语法,因此它需要与identifier->keyword>一起位于语法的begin
中:
(begin-for-syntax
;; identifier->keyword : Identifer -> (Syntaxof Keyword)
(define (identifier->keyword id)
(datum->syntax id (string->keyword (symbol->string (syntax-e id))) id id))
;; for use in define
(define-syntax-class arg-spec
[pattern name:id
;; a sequence of one thing
#:with (norm ...) #'(name)]
[pattern [name:id default-val:expr]
#:when (equal? #\[ (syntax-property this-syntax 'paren-shape))
#:with name-kw (identifier->keyword #'name)
;; a sequence of two things
#:with (norm ...) #'(name-kw [name default-val])]))
然后您可以这样定义define
,使用arg:arg spec
指定arg
使用arg spec
语法类
(define-simple-macro (define (fn arg:arg-spec ...) body ...+)
(old-define (fn arg.norm ... ...) body ...))
对于给定的arg
,arg.norm…
要么是一个事物的序列(对于位置参数)要么是两个事物的序列(对于命名参数)。然后由于arg
本身可以出现任意次数,arg.norm…
在另一个省略号下,因此arg.norm
在两个省略号下
#%app
宏将使用类似的语法类,但会稍微复杂一些,因为arg
s可以是任意表达式,它需要确保普通表达式不使用方括号
(begin-for-syntax
;; for use in #%app
(define-syntax-class arg
[pattern arg:expr
#:when (not (equal? #\[ (syntax-property this-syntax 'paren-shape)))
;; a sequence of one thing
#:with (norm ...) #'(arg)]
[pattern [name:id arg:expr]
#:when (equal? #\[ (syntax-property this-syntax 'paren-shape))
#:with name-kw (identifier->keyword #'name)
;; a sequence of two things
#:with (norm ...) #'(name-kw arg)]))
同样,一个参数有两个变量。第一个变量需要是一个不使用方括号的表达式,第二个变量需要是一个名称和一个用方括号括起来的表达式
(begin-for-syntax
;; for use in #%app
(define-syntax-class arg
[pattern arg:expr
#:when (not (equal? #\[ (syntax-property this-syntax 'paren-shape)))
;; a sequence of one thing
#:with (norm ...) #'(arg)]
[pattern [name:id arg:expr]
#:when (equal? #\[ (syntax-property this-syntax 'paren-shape))
#:with name-kw (identifier->keyword #'name)
;; a sequence of two things
#:with (norm ...) #'(name-kw arg)]))
而#%app
宏本身需要确保它不与方括号一起使用。它可以使用#:fail when
子句:
(require (for-syntax (only-in racket [#%app app])))
(define-simple-macro (#%app fn arg:arg ...)
#:fail-when (app equal? #\[ (app syntax-property this-syntax 'paren-shape))
"function applications can't use `[`"
(old-#%app fn arg.norm ... ...))
现在可以使用命名参数定义greet4
,但它也可以使用带位置参数的string append
> (define (greet4 [hi "hello"] [given "Joe"] [surname "Smith"])
(string-append hi ", " given " " surname))
> (greet4 [surname "Watchman"] [hi "hey"] [given "Robert"])
"hey, Robert Watchman"
与前面一样,省略参数会导致它使用默认值
> (greet4 [hi "hey"] [given "Robert"])
"hey, Robert Smith"
现在不同的是位置参数起作用
> (displayln (string-append "FROGGY" "!"))
FROGGY!
那
> (old-define display-greeting (compose displayln greet4))
> (display-greeting [hi "hey"] [given "Robert"])
hey, Robert Smith
(define-simple-macro (define (fn arg:arg-spec ...) body ...+)
(old-define (fn arg.norm ... ...) body ...))
(define-syntax-parser define
[(define (fn arg:arg-spec ...) body ...+)
#'(old-define (fn arg.norm ... ...) body ...)])
(define-syntax-parser define
[(define x:id val:expr)
#'(old-define x val)]
[(define (fn arg:arg-spec ...) body ...+)
#'(old-define (fn arg.norm ... ...) body ...)])