Racket 中函数参数的关键字和默认值的宏 [英] Macro for keyword and default values of function arguments in Racket

查看:42
本文介绍了Racket 中函数参数的关键字和默认值的宏的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

可以在 Racket 函数中使用关键字和默认参数,如本页所示:https://docs.racket-lang.org/guide/lambda.html

Keyword and default arguments can be used in Racket functions as shown on this page: https://docs.racket-lang.org/guide/lambda.html

(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"

既然据说 Racket 可以很容易地创建新的语言定义,那么是否可以创建一个宏来定义函数如下:

Since Racket is said to be able to create new language definitions easily, is it possible to create a macro so that functions can be defined as follows:

(define (greet2 (hi "hello") (given "Joe") (surname "Smith"))
    (string-append hi ", " given " " surname))

应该可以以任何顺序调用带参数的函数,如下所示:

It should be possible to call the functions with arguments in any order as follows:

(greet2 (surname "Watchman") (hi "hi") (given "Robert") )

只是为了澄清,以下工作:

Just to clarify, following works:

(define (greet3 #:hi [hi "hello"] #:given  [given "Joe"] #:surname  [surname "Smith"])
    (string-append hi ", " given " " surname))

(greet3 #:surname "Watchman" #:hi "hey" #:given "Robert" )

但我想要以下工作(括号可能是 () 或 [] 甚至 {} ):

But I want following to work (parentheses may be () or [] or even {} ):

(define (greet4 [hi "hello"]  [given "Joe"]  [surname "Smith"])
    (string-append hi ", " given " " surname))

(greet4 [surname "Watchman"] [hi "hey"] [given "Robert"])

基本上,我想去掉#:surname"部分(因为它看起来重复)以提高打字的便利性.

Basically, I want to get rid of "#:surname" part (since it appears repetitive) to improve ease of typing.

如何创建这样的宏?我尝试了以下行的一些代码:

How can such a macro be created? I attempted some code on the lines of:

(define-syntax-rule (myfn (arg1 val1) (arg2 val2)  ...)   
    (myfn #:arg1 val1 #:arg2 val2 ...))

但它不起作用.

感谢您的评论/回答.

我修改了@AlexKnauth 的回答中的代码以使用 {} 而不是 [],这也很有效:

I modified the code from answer by @AlexKnauth to use {} instead of [] and this also works well:

(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"

并且参数顺序具有灵活性:

And there is flexibility of order of arguments:

> (greet5 {surname "Watchman"} "Howya" {given "Robert"})
"Howya, Robert Watchman"

现在简单的定义语句不起作用:

Now simple define statements are not working:

(define x 0)
  define: bad syntax in: (define x 0)

代替 (old-define x 0) 起作用.

推荐答案

你可以这样做,但你需要使用 define-simple-macro 和一个 identifier 稍微复杂一些-> 关键字 辅助函数.

You can do this, but you'll need something slightly more complicated using define-simple-macro and an identifier->keyword helper function.

你可以定义你自己的define 表单和你自己的#%app 用于函数应用,但要做到这一点,你需要扩展到球拍的旧版本,所以您需要使用 only-in 要求形式导入重命名的版本.

You can define your own define form and your own #%app to use for function applications, but to do that you need to expand into racket's old versions, so you need import renamed versions, using the only-in require form.

您还需要将 identifier->keyword 函数映射到所有标识符上.一个有用的函数是 syntax/stx 中的 stx-map.它类似于 map,但它也适用于语法对象.

You'll also need to map the identifier->keyword function over all the identifiers. A useful function for that is stx-map from syntax/stx. It's similar to map, but it works on syntax objects as well.

#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

To define a helper function for a macro to use to transform the syntax, you need to put it inside a 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 辅助函数.

This answer defines two versions of this: one that supports only named arguments, and one that supports both named and positional arguments. However, both of them will use the identifier->keyword helper function.

这个新版本的 define 采用 arg-name 并使用 identifier->keyword 辅助函数将它们转换为关键字,但是因为它需要转换它们的语法列表,所以它使用 stx-map.

This new version of define takes the arg-names and transforms them into keywords using the identifier->keyword helper function, but since it needs to transform a syntax-list of them, it uses stx-map.

然后将关键字与 [arg-name default-val] 对组合在一起以创建 arg-kw [arg-name default-val] 序列.使用具体代码,这会将 #:hi[hi "hello"] 分组以创建 #:hi [hi "hello"]<的序列/code> 这是旧的定义形式所期望的.

It then groups the keywords together with the [arg-name default-val] pairs to create sequences of arg-kw [arg-name default-val]. With concrete code, this will group the #:hi with the [hi "hello"] to create sequences of #:hi [hi "hello"] which is what the old define form expects.

(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"]).

This defines an #%app macro that will be inserted implicitly on all function applications. (f stuff ...) will expand into (#%app f stuff ...), so (greet4 [hi "hey"]) will expand into (#%app greet4 [hi "hey"]).

这个宏将(#%app greet4 [hi "hey"]) 转换成(old-#%app greet4 #:hi "hey").>

This macro transforms (#%app greet4 [hi "hey"]) into (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 宏:

These impliticly use the new #%app macro defined above:

> (greet4 [surname "Watchman"] [hi "hey"] [given "Robert"])
"hey, Robert Watchman"

省略参数使其使用默认值:

Omitting arguments makes it use the default:

> (greet4 [hi "hey"] [given "Robert"])
"hey, Robert Smith"

greet4 这样的函数仍然可以在高阶函数中使用:

And functions like greet4 can still be used within higher-order functions:

> (old-define display-greeting (old-#%app compose displayln greet4))
> (display-greeting [hi "hey"] [given "Robert"])
hey, Robert Smith

命名和位置参数

上面的宏只支持命名参数,所以不能定义使用位置参数的函数.但是,可以在同一个宏中同时支持位置参数和命名参数.

Both named and positional arguments

The macros above support only named arguments, so functions that use positional arguments instead can't be defined used with them. However, it's possible to support both positional arguments and named arguments in the same macro.

要做到这一点,我们必须使方括号 [] 特殊",以便 define#%app 可以区分命名参数和表达式.要做到这一点,我们可以使用 (syntax-property stx 'paren-shape),如果 stx 是字符,它将返回字符 #\[用方括号书写.

To do this, we would have to make square brackets [ and ] "special" so that define and #%app can tell between a named argument and an expression. To do that, we can use (syntax-property stx 'paren-shape), which will return the character #\[ if stx was written with square brackets.

因此要在 define 中指定位置参数,您只需使用普通标识符,而要使用命名参数,您将使用方括号.因此,参数规范可以是这些变体之一.你可以用语法类来表达.

So to specify a positional argument in a define, you would just use a normal identifier, and to use a named argument, you would use square brackets. So an argument specification could be either one of those variants. You can express that with a syntax class.

因为它被宏用来转换语法,所以它需要在begin-for-syntaxidentifier->keyword中:>

Since it is used by the macro to transform the syntax, it needs to be in a begin-for-syntax along with 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-规范 语法类.

And then you can define define like this, using arg:arg-spec to specify that arg uses the arg-spec syntax class.

(define-simple-macro (define (fn arg:arg-spec ...) body ...+)
  (old-define (fn arg.norm ... ...) body ...))

对于给定的 argarg.norm ... 要么是一个事物的序列(对于位置参数),要么是两个事物的序列(对于命名参数)).然后由于 arg 本身可以出现任意次数,arg.norm ... 在另一个省略号下,所以 arg.norm 在两个椭圆.

For a given arg, arg.norm ... is either a sequence of one thing (for positional arguments) or a sequence of two things (for named arguments). Then since arg itself can appear any number of times, arg.norm ... is under another ellipsis, so that arg.norm is under two ellipses.

#%app 宏将使用类似的语法类,但会稍微复杂一些,因为 arg 可以是任意表达式,并且需要使确保普通表达式不使用方括号.

The #%app macro will use a similar syntax class, but it will be slightly more complicated because the args can be arbitrary expressions, and it needs to make sure that normal expressions don't use square brackets.

同样,一个参数有两种变体.第一个变量需要是一个使用方括号的表达式,第二个变量需要是一个名称和一个包含在方括号中的表达式.

Again, an argument has two variants. The first variant needs to be an expression that doesn't use square brackets, and the second variant needs to be a name and an expression wrapped in square brackets.

(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 子句做到这一点:

And the #%app macro itself needs to make sure that it's not used with square brackets. It can do that with a #:fail-when clause:

(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.

Now greet4 can be defined using named arguments, but it can also use string-append with positional arguments.

> (define (greet4 [hi "hello"]  [given "Joe"]  [surname "Smith"])
    (string-append hi ", " given " " surname))
> (greet4 [surname "Watchman"] [hi "hey"] [given "Robert"])
"hey, Robert Watchman"

就像以前一样,省略参数会导致它使用默认值.

Just like before, omitting arguments causes it to use the default.

> (greet4 [hi "hey"] [given "Robert"])
"hey, Robert Smith"

现在不同的是位置参数起作用了,

What's different now is that positional arguments work,

> (displayln (string-append "FROGGY" "!"))
FROGGY!

而且方括号[]不能再用于表达式了.

And that square brackets [ and ] can't be used for expression any more.

> (displayln [string-append "FROGGY" "!"])
;#%app: expected arg
> [string-append "FROGGY" "!"]
;#%app: function applications can't use `[`

和以前一样,greet4 可以用在 compose 等高阶函数中.

Just like before, greet4 can be used in higher-order functions like compose.

> (old-define display-greeting (compose displayln greet4))
> (display-greeting [hi "hey"] [given "Robert"])
hey, Robert Smith

修改它以支持非函数定义

上面的 define 宏专门用于函数定义,以保持简单.但是,您也可以通过使用 define-syntax-parser 并指定多个 case 来支持非函数定义.

Modifying it to support non-function definitions

The define macros above were specialized for function definitions, to keep it simple. However, you can support non-function definitions as well by using define-syntax-parser and specifying multiple cases.

define-simple-macro 在这里定义

(define-simple-macro (define (fn arg:arg-spec ...) body ...+)
  (old-define (fn arg.norm ... ...) body ...))

等价于在一个子句中使用define-syntax-parser.

Is equivalent to using define-syntax-parser with one clause.

(define-syntax-parser define
  [(define (fn arg:arg-spec ...) body ...+)
   #'(old-define (fn arg.norm ... ...) body ...)])

所以要支持多个子句,可以这样写:

So to support multiple clauses, you can write:

(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 ...)])

那么这也将支持像(define x 0)这样的定义.

Then this will also support definitions like (define x 0).

这篇关于Racket 中函数参数的关键字和默认值的宏的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆