是否有一种速记方法来更新球拍中的特定结构字段?

Posted

技术标签:

【中文标题】是否有一种速记方法来更新球拍中的特定结构字段?【英文标题】:Is there a shorthand way to update a specific struct field in racket? 【发布时间】:2015-04-20 11:59:25 【问题描述】:

假设我有一个包含许多字段的结构:

(struct my-struct (f1 f2 f3 f4))

如果我要返回一个更新了 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-updatemy-struct-f2-set 应该只是返回 s 的副本,其中 f2 字段已更新。

在 Haskell 中,我知道完成这项工作的“镜头”库。我只是想知道是否有一些类似的方法可以用于球拍。谢谢。

【问题讨论】:

【参考方案1】:

你知道吗?这是一个非常好的主意。事实上,在一些情况下我想要这个功能,但我没有。坏消息是,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 安装。

【讨论】:

请注意,这个宏和 struct-copy 一样,会丢失子结构字段。 @SamTobin-Hochstadt 是的。有一种方法可以在运行时获取有关结构实例的信息会很好,尽管我猜这可能违反不透明结构的概念。【参考方案2】:

Alexis 的微距非常棒,Greg 正确地指出了 struct-copymatch+struct*,但由于您在示例中特别提到了镜头,我将指出 there is now a lens package for Racket(免责声明:我写了很多)。它为您的用例提供了struct/lensdefine-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 会更简单。但是,如果您有大量不同风格的嵌套数据结构,那么组合镜头的能力使它们成为完成这项工作的强大工具。

【讨论】:

【参考方案3】:

我喜欢 Alexis 的宏!它有更多你想要的“镜头”味道。

我还想指出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)

更新:再想一想,这里还有一些想法。

您也可以使用matchstruct* 模式进行更新:

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

当然,设置是你给更新一个特殊情况 const函数:

(struct-update my-struct s f2 (const 42))
;;=> (my-struct 1 42 3 4)

最后,这就像struct-update,但返回一个更新函数,本着 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)

我并不是说这些都是惯用的或有效的。但这是可能的。 :)

【讨论】:

以上是关于是否有一种速记方法来更新球拍中的特定结构字段?的主要内容,如果未能解决你的问题,请参考以下文章

如何检查 json 是不是与结构/结构字段匹配

是否有一种更简洁的 Dapper 方法来仅更新随 Dapper 更改的列?

boto3是否有一种方法,如果在aws中进行更改,describe_instances()会自动更新?

反射以获取字段标记

使用 Ajax 更新特定对象中单个字段的正确方法

go语言学习-结构体