关键字和默认参数可以在Racket函数中使用,如本页所示:https://docs.racket-lang.org/guide/lambda.html
JObject
由于据说Racket能够轻松创建新的语言定义,因此可以创建一个宏,以便可以按如下方式定义函数:
(define greet
(lambda (#:hi [hi "Hello"] given #:last [surname "Smith"])
(string-append hi ", " given " " surname)))
> (greet "John")
"Hello, John Smith"
> (greet "Karl" #:last "Marx")
"Hello, Karl Marx"
> (greet "John" #:hi "Howdy")
"Howdy, John Smith"
> (greet "Karl" #:last "Marx" #:hi "Guten Tag")
"Guten Tag, Karl Marx"
应该可以按任何顺序调用带有参数的函数,如下所示:
(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" )
基本上,我想摆脱“#:surname”部分(因为它看似重复)以提高打字的便利性。
如何创建这样的宏?我尝试了一些代码:
(define (greet4 [hi "hello"] [given "Joe"] [surname "Smith"])
(string-append hi ", " given " " surname))
(greet4 [surname "Watchman"] [hi "hey"] [given "Robert"])
但它不起作用。
感谢您的意见/答案。
修改:
我修改了@AlexKnauth回答的代码,使用{}代替[],这也很有效:
(define-syntax-rule (myfn (arg1 val1) (arg2 val2) ...)
(myfn #:arg1 val1 #:arg2 val2 ...))
使用示例:
(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 x 0)
define: bad syntax in: (define x 0)
。
答案 0 :(得分:1)
你可以这样做,但是你需要使用define-simple-macro
和identifier->keyword
辅助函数稍微复杂一些。
您可以定义自己的define
表单和自己的#%app
以用于功能应用程序,但要做到这一点,您需要扩展到球拍的旧版本,因此您需要重命名导入版本,使用only-in
要求表单。
您还需要在所有标识符上映射identifier->keyword
函数。一个有用的功能是来自stx-map
的{{1}}。它与syntax/stx
类似,但它也适用于语法对象。
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
begin-for-syntax
这个答案定义了两个版本:一个只支持命名参数,另一个支持命名和位置参数。但是,它们都将使用(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
使用define
并使用arg-name
辅助函数将它们转换为关键字,但由于它需要转换它们的语法列表,因此它使用identifier->keyword
。
然后,它会将关键字与stx-map
对组合在一起,以创建[arg-name default-val]
的序列。使用具体代码,这会将arg-kw [arg-name default-val]
与#:hi
分组,以创建[hi "hello"]
的序列,这是旧定义表单所期望的。
#:hi [hi "hello"]
这定义了一个(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")
使用新的(require (for-syntax (only-in racket [#%app app])))
(define-simple-macro (#%app fn [arg-name val] ...)
;; same stx-map as before, but need to use racket's `#%app`, renamed to `app` here, explicitly
#:with (arg-kw ...) (app stx-map identifier->keyword #'(arg-name ...))
;; group the arg-kws and vals together as sequences
#:with ((arg-kw/val ...) ...) #'((arg-kw val) ...)
;; expand to old-#%app
(old-#%app fn arg-kw/val ... ...))
表单:
define
这些不明智地使用了上面定义的新> (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
和[
"特殊"这样]
和define
可以在命名参数和表达式之间进行区分。为此,我们可以使用#%app
,如果使用方括号编写(syntax-property stx 'paren-shape)
,则会返回字符#\[
。
因此,要在stx
中指定位置参数,您只需使用普通标识符,并使用命名参数,您可以使用方括号。因此,参数规范可以是这些变体之一。你可以用syntax class来表达。
由于宏使用它来转换语法,因此它需要与define
一起放在begin-for-syntax
中:
identifier->keyword
然后您可以使用(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
(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)]))
宏本身需要确保 it 不使用方括号。它可以通过#%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!
和[
不能再用于表达。
]
就像之前一样,> (displayln [string-append "FROGGY" "!"])
;#%app: expected arg
> [string-append "FROGGY" "!"]
;#%app: function applications can't use `[`
可用于greet4
等高阶函数。
compose
上面的> (old-define display-greeting (compose displayln greet4))
> (display-greeting [hi "hey"] [given "Robert"])
hey, Robert Smith
宏专门用于函数定义,以保持简单。但是,您也可以使用define
并指定多个案例来支持非函数定义。
此处define-syntax-parser
定义
define-simple-macro
相当于将(define-simple-macro (define (fn arg:arg-spec ...) body ...+)
(old-define (fn arg.norm ... ...) body ...))
与一个子句一起使用。
define-syntax-parser
所以为了支持多个子句,你可以写:
(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 ...)])
等定义。