添加结构体字段

3

所以,我有一个Racket的struct,叫做stats:

(struct stats (str con dex int wis cha))

我有一个函数add-stats:


(define (modify-stats mods base)
   (stats (+ (stats-str mods)
             (stats-str base))
          (+ (stats-con mods)
             (stats-con base))
          (+ (stats-dex mods)
             (stats-dex base))
          (+ (stats-int mods)
             (stats-int base))
          (+ (stats-wis mods)
             (stats-wis base))
          (+ (stats-cha mods)
             (stats-cha base))))

显然这很混乱,而且涉及到很多不必要的重复。我成功将其简化为更易读的版本:
(define (modify-stats mods base)
    (define (add-stat statid)
        (+ (statid mods)
           (statid base)))

    (stats (add-stat stats-str)
           (add-stat stats-con)
           (add-stat stats-dex)
           (add-stat stats-int)
           (add-stat stats-wis)
           (add-stat stats-cha)))

但是这里仍然有很多"stat(s)"的重复。有没有更简洁的方法可以对相同类型的两个结构体的字段执行操作?


更新: 我已经成功改进了它,就像这样:

(define (stat a-stat stats)
  (match a-stat
    ["str" (stats-str stats)]
    ["con" (stats-con stats)]
    ["dex" (stats-dex stats)]
    ["int" (stats-int stats)]
    ["wis" (stats-wis stats)]
    ["cha" (stats-cha stats)]
    [_ (error "Not a stat!")]))

(define (modify-stats mods base)
  (define (add-stat string)
    (+ (stat string mods)
       (stat string base)))
  (stats (add-stat "str")
         (add-stat "con")
         (add-stat "dex")
         (add-stat "int")
         (add-stat "wis")
         (add-stat "cha")))

也许是因为我不够聪明,但第二种执行操作的方式读起来几乎和我能想象的代码读起来一样清晰。除非有令人信服的理由,例如许多具有类似操作的不同结构,那看起来就像是我想要维护的东西。如果通用方法是适当的,那么我希望传递操作,如 add-stat,而不是硬编码它。这个问题似乎更适合 StackExchange 代码审查,因为代码是工作的。 - ben rudgers
1个回答

4

不使用反射,这里有一种方法可以做到:

(define (modify-stats mods base)
  (define (get-fields obj)
    (map (lambda (getter) (getter obj))
         (list stats-str stats-con stats-dex stats-int stats-wis stats-cha)))
  (apply stats (map + (get-fields mods) (get-fields base))))

虽然我不太建议使用宏来提高性能,但这个宏生成的代码和OP最初的版本完全相同:

(require (for-syntax racket/syntax))
(define modify-stats
  (let-syntax
      ((bump (lambda (stx)
               (define (bump-attr attr)
                 (with-syntax ((getter (format-id attr "stats-~a" attr #:source attr)))
                   #'(+ (getter mods) (getter base))))
               (syntax-case stx ()
                 ((_ attr ...)
                  (with-syntax (((bumped ...) (map bump-attr (syntax->list #'(attr ...)))))
                    #'(lambda (mods base)
                        (stats bumped ...))))))))
    (bump str con dex int wis cha)))

1
这个版本比OP的第一个版本慢了大约17倍,比OP的第二个版本慢了3倍。所以如果性能很重要,我会坚持使用最初的版本。 - uselpa
1
完全可以理解。第一个版本很容易内联。第二个版本增加了更多的间接成本,而我的则将其提升到了另一个层次。 :-) - C. K. Young
同意。因此,“理想”的解决方案将是一种语法扩展,使用反射生成第一个版本。您认为这是可行的吗? - uselpa
我正在编写一个概念验证宏来实现这样的事情,但不使用反射(因为那也有成本)。 - C. K. Young
好的,宏现在已经编写完成。虽然不是世界上最优雅的东西,但希望仍然比原来少了一些代码重复。;-) - C. K. Young
2
接受挑战,给你点赞;-) - uselpa

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接