关键字的宏和Racket中函数参数的默认值

时间:2016-07-30 13:25:46

标签: scheme racket

关键字和默认参数可以在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)

1 个答案:

答案 0 :(得分:1)

你可以这样做,但是你需要使用define-simple-macroidentifier->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 ...)]) 等定义。