Scheme 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") ) 应该可以按如下任

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