有没有 shorthand 方法来更新球拍中的特定结构字段?
Is there a shorthand way to update a specific struct field in racket?
假设我有一个包含很多字段的结构:
(struct my-struct (f1 f2 f3 f4))
如果我要 return 更新 f2
的新结构,我必须重新表述所有其他字段:
(define s (my-struct 1 2 3 4))
(my-struct (my-struct-f1 s)
(do-something-on (my-struct-f2 s))
(my-struct-f3 s)
(my-struct-f4 s))
这是多余的,如果我更新字段的数量或更改它们的顺序,将会成为错误的来源。
我真的很想知道是否有这样的方法可以更新结构的特定字段,例如:
(my-struct-f2-update (my-struct 1 2 3 4)
(lambda (f2) (* f2 2)))
;; => (my-struct 1 4 3 4)
或者我可以将它们设置为新值:
(define s (my-struct 1 2 3 4)
(my-struct-f2-set s (* (my-struct-f2 s) 2))
;; => (my-struct 1 4 3 4)
注意,这不是变异s
; my-struct-f2-update
和 my-struct-f2-set
应该只是 return 更新了 f2
字段的 s
副本。
在 Haskell 中,我知道 'lens' 库可以完成这项工作。我只是想知道是否有一些类似的方法可以用于球拍。谢谢。
你知道吗?这是一个非常好的主意。事实上,在一些情况下我想要这个功能,但我没有。坏消息是 Racket 没有提供任何此类内容。好消息是Racket有宏!
我送给你define-struct-updaters
!
(require (for-syntax racket/list
racket/struct-info
racket/syntax
syntax/parse))
(define-syntax (define-struct-updaters stx)
(syntax-parse stx
[(_ name:id)
; this gets compile-time information about the struct
(define struct-info (extract-struct-info (syntax-local-value #'name)))
; we can use it to get the constructor, predicate, and accessor functions
(define/with-syntax make-name (second struct-info))
(define/with-syntax name? (third struct-info))
(define accessors (reverse (fourth struct-info)))
(define/with-syntax (name-field ...) accessors)
; we need to generate setter and updater identifiers from the accessors
; we also need to figure out where to actually put the new value in the argument list
(define/with-syntax ([name-field-set name-field-update
(name-field-pre ...) (name-field-post ...)]
...)
(for/list ([accessor (in-list accessors)]
[index (in-naturals)])
(define setter (format-id stx "~a-set" accessor #:source stx))
(define updater (format-id stx "~a-update" accessor #:source stx))
(define-values (pre current+post) (split-at accessors index))
(list setter updater pre (rest current+post))))
; now we just need to generate the actual function code
#'(begin
(define/contract (name-field-set instance value)
(-> name? any/c name?)
(make-name (name-field-pre instance) ...
value
(name-field-post instance) ...))
...
(define/contract (name-field-update instance updater)
(-> name? (-> any/c any/c) name?)
(make-name (name-field-pre instance) ...
(updater (name-field instance))
(name-field-post instance) ...))
...)]))
如果你不熟悉宏,它可能看起来有点吓人,但它实际上并不是一个复杂的宏。幸运的是,您无需了解它的工作原理即可使用它。以下是您的操作方式:
(struct point (x y) #:transparent)
(define-struct-updaters point)
现在您可以随意使用所有相关的功能设置器和更新器。
> (point-x-set (point 1 2) 5)
(point 5 2)
> (point-y-update (point 1 2) add1)
(point 1 3)
我相信已经有一些重新设计 Racket 结构系统的理论计划,我认为这将是一个有价值的补充。在那之前,请随意使用此解决方案。我已将此答案中的代码作为 the struct-update
package 提供,可以使用 raco pkg install struct-update
.
安装
我喜欢亚历克西斯的宏!它有更多您想要的 "lens" 风味。
我也想指出struct-copy
。给定:
#lang racket
(struct my-struct (f1 f2 f3 f4) #:transparent)
(define s (my-struct 1 2 3 4))
您可以使用struct-copy
设置一个值:
(struct-copy my-struct s [f2 200])
;;=> (my-struct 1 200 3 4)
或更新值:
(struct-copy my-struct s [f2 (* 100 (my-struct-f2 s))])
;;=> (my-struct 1 200 3 4)
更新:想多了,这里有几个想法。
您也可以使用 match
的 struct*
模式进行更新:
(match s
[(struct* my-struct ([f2 f2]))
(struct-copy my-struct s [f2 (* 100 f2)])])
当然,这很冗长。另一方面 struct*
模式使得使用更简单的方法定义宏变得容易
define-syntax-rule
:
;; Given a structure type and an instance of it, a field-id, and a
;; function, return a new structure instance where the field is the
;; value of applying the function to the original value.
(define-syntax-rule (struct-update struct-type st field-id fn)
(match st
[(struct* struct-type ([field-id v]))
(struct-copy struct-type st [field-id (fn v)])]))
(struct-update my-struct s f2 (curry * 100))
;;=> (my-struct 1 200 3 4)
当然,setting是你给update a的特例
const
函数:
(struct-update my-struct s f2 (const 42))
;;=> (my-struct 1 42 3 4)
最后,这类似于 struct-update
,但是 returns 一个更新函数,本着 Alexis 宏的精神:
(define-syntax-rule (struct-updater struct-type field-id)
(λ (st fn)
(struct-update struct-type st field-id fn)))
(define update-f2 (struct-updater my-struct f2))
(update-f2 s (curry * 100))
;;=> (my-struct 1 200 3 4)
我并不是说这些是惯用的或有效的。但这是可能的。 :)
Alexis 的宏很棒,Greg 正确地指出了 struct-copy
和 match+struct*
,但是由于您在示例中特别提到了镜头,我会指出 there is now a lens package for Racket(免责声明:我写了很多)。它为您的用例提供 struct/lens
和 define-struct-lenses
宏:
> (struct/lens foo (a b c))
> (lens-view foo-a-lens (foo 1 2 3))
1
> (lens-set foo-a-lens (foo 1 2 3) 'a)
(foo 'a 2 3)
> (lens-transform foo-a-lens (foo 1 2 3) number->string)
(foo "1" 2 3)
define-struct-lenses
允许您将镜头与结构分开定义:
> (struct foo (a b c))
> (define-struct-lenses foo)
以上等同于(struct/lens foo (a b c))
。如果您仅对与其他类型的结构隔离的结构进行操作,则使用 define-struct-updaters
会更简单。但是,如果你有很多不同风格的嵌套数据结构,组合镜头的能力使它们成为这项工作的强大工具。
假设我有一个包含很多字段的结构:
(struct my-struct (f1 f2 f3 f4))
如果我要 return 更新 f2
的新结构,我必须重新表述所有其他字段:
(define s (my-struct 1 2 3 4))
(my-struct (my-struct-f1 s)
(do-something-on (my-struct-f2 s))
(my-struct-f3 s)
(my-struct-f4 s))
这是多余的,如果我更新字段的数量或更改它们的顺序,将会成为错误的来源。
我真的很想知道是否有这样的方法可以更新结构的特定字段,例如:
(my-struct-f2-update (my-struct 1 2 3 4)
(lambda (f2) (* f2 2)))
;; => (my-struct 1 4 3 4)
或者我可以将它们设置为新值:
(define s (my-struct 1 2 3 4)
(my-struct-f2-set s (* (my-struct-f2 s) 2))
;; => (my-struct 1 4 3 4)
注意,这不是变异s
; my-struct-f2-update
和 my-struct-f2-set
应该只是 return 更新了 f2
字段的 s
副本。
在 Haskell 中,我知道 'lens' 库可以完成这项工作。我只是想知道是否有一些类似的方法可以用于球拍。谢谢。
你知道吗?这是一个非常好的主意。事实上,在一些情况下我想要这个功能,但我没有。坏消息是 Racket 没有提供任何此类内容。好消息是Racket有宏!
我送给你define-struct-updaters
!
(require (for-syntax racket/list
racket/struct-info
racket/syntax
syntax/parse))
(define-syntax (define-struct-updaters stx)
(syntax-parse stx
[(_ name:id)
; this gets compile-time information about the struct
(define struct-info (extract-struct-info (syntax-local-value #'name)))
; we can use it to get the constructor, predicate, and accessor functions
(define/with-syntax make-name (second struct-info))
(define/with-syntax name? (third struct-info))
(define accessors (reverse (fourth struct-info)))
(define/with-syntax (name-field ...) accessors)
; we need to generate setter and updater identifiers from the accessors
; we also need to figure out where to actually put the new value in the argument list
(define/with-syntax ([name-field-set name-field-update
(name-field-pre ...) (name-field-post ...)]
...)
(for/list ([accessor (in-list accessors)]
[index (in-naturals)])
(define setter (format-id stx "~a-set" accessor #:source stx))
(define updater (format-id stx "~a-update" accessor #:source stx))
(define-values (pre current+post) (split-at accessors index))
(list setter updater pre (rest current+post))))
; now we just need to generate the actual function code
#'(begin
(define/contract (name-field-set instance value)
(-> name? any/c name?)
(make-name (name-field-pre instance) ...
value
(name-field-post instance) ...))
...
(define/contract (name-field-update instance updater)
(-> name? (-> any/c any/c) name?)
(make-name (name-field-pre instance) ...
(updater (name-field instance))
(name-field-post instance) ...))
...)]))
如果你不熟悉宏,它可能看起来有点吓人,但它实际上并不是一个复杂的宏。幸运的是,您无需了解它的工作原理即可使用它。以下是您的操作方式:
(struct point (x y) #:transparent)
(define-struct-updaters point)
现在您可以随意使用所有相关的功能设置器和更新器。
> (point-x-set (point 1 2) 5)
(point 5 2)
> (point-y-update (point 1 2) add1)
(point 1 3)
我相信已经有一些重新设计 Racket 结构系统的理论计划,我认为这将是一个有价值的补充。在那之前,请随意使用此解决方案。我已将此答案中的代码作为 the struct-update
package 提供,可以使用 raco pkg install struct-update
.
我喜欢亚历克西斯的宏!它有更多您想要的 "lens" 风味。
我也想指出struct-copy
。给定:
#lang racket
(struct my-struct (f1 f2 f3 f4) #:transparent)
(define s (my-struct 1 2 3 4))
您可以使用struct-copy
设置一个值:
(struct-copy my-struct s [f2 200])
;;=> (my-struct 1 200 3 4)
或更新值:
(struct-copy my-struct s [f2 (* 100 (my-struct-f2 s))])
;;=> (my-struct 1 200 3 4)
更新:想多了,这里有几个想法。
您也可以使用 match
的 struct*
模式进行更新:
(match s
[(struct* my-struct ([f2 f2]))
(struct-copy my-struct s [f2 (* 100 f2)])])
当然,这很冗长。另一方面 struct*
模式使得使用更简单的方法定义宏变得容易
define-syntax-rule
:
;; Given a structure type and an instance of it, a field-id, and a
;; function, return a new structure instance where the field is the
;; value of applying the function to the original value.
(define-syntax-rule (struct-update struct-type st field-id fn)
(match st
[(struct* struct-type ([field-id v]))
(struct-copy struct-type st [field-id (fn v)])]))
(struct-update my-struct s f2 (curry * 100))
;;=> (my-struct 1 200 3 4)
当然,setting是你给update a的特例
const
函数:
(struct-update my-struct s f2 (const 42))
;;=> (my-struct 1 42 3 4)
最后,这类似于 struct-update
,但是 returns 一个更新函数,本着 Alexis 宏的精神:
(define-syntax-rule (struct-updater struct-type field-id)
(λ (st fn)
(struct-update struct-type st field-id fn)))
(define update-f2 (struct-updater my-struct f2))
(update-f2 s (curry * 100))
;;=> (my-struct 1 200 3 4)
我并不是说这些是惯用的或有效的。但这是可能的。 :)
Alexis 的宏很棒,Greg 正确地指出了 struct-copy
和 match+struct*
,但是由于您在示例中特别提到了镜头,我会指出 there is now a lens package for Racket(免责声明:我写了很多)。它为您的用例提供 struct/lens
和 define-struct-lenses
宏:
> (struct/lens foo (a b c))
> (lens-view foo-a-lens (foo 1 2 3))
1
> (lens-set foo-a-lens (foo 1 2 3) 'a)
(foo 'a 2 3)
> (lens-transform foo-a-lens (foo 1 2 3) number->string)
(foo "1" 2 3)
define-struct-lenses
允许您将镜头与结构分开定义:
> (struct foo (a b c))
> (define-struct-lenses foo)
以上等同于(struct/lens foo (a b c))
。如果您仅对与其他类型的结构隔离的结构进行操作,则使用 define-struct-updaters
会更简单。但是,如果你有很多不同风格的嵌套数据结构,组合镜头的能力使它们成为这项工作的强大工具。