gpt4 book ai didi

macros - 使用宏绑定(bind) getter 和 setter

转载 作者:行者123 更新时间:2023-12-05 01:36:33 24 4
gpt4 key购买 nike

我的大部分应用程序状态都存储在一个大型复杂 map 中。出于这个问题的目的,我将使用一个简单的结构:

(def data
{:a 1
:b {:c {:d 3}}})

我有大量的函数都遵循相同的模式:

(defn update-map
[my-map val]
(let [a (:a my-map)
d (-> my-map :b :c :d)]
(assoc-in
(assoc my-map :a (+ a val))
[:b :c :d] (+ d val))))

我从 map 中检索一个或多个值,执行一些计算,然后创建一个具有更新值的新 map 。这种方法有两个问题:

  • 我有很多跨不同函数定义的重复 let 绑定(bind)
  • 如果 map 的架构发生变化,我将有很多代码需要重构

我编写了一个宏来减少定义这些函数所需的样板代码。它通过查找预定义的 getter 和 setter 函数,并自动生成一个 let block 来工作:

(def getters
{'a #(:a %)
'd #(-> % :b :c :d)})

(def setters
{'a #(assoc % :a %2)
'd #(assoc-in % [:b :c :d] %2)})

(defmacro def-map-fn
[name [& args] [& fields] & code]
(let [my-map 'my-map
lookup #(reduce % [] fields)
getter-funcs (lookup #(conj % %2 (list (getters %2) my-map)))
setter-funcs (lookup #(conj % (symbol (str "update-" %2)) (setters %2)))]
`(defn ~name [~my-map ~@args]
(let [~@getter-funcs ~@setter-funcs]
~@code))))

我现在可以更优雅地定义我的函数:

(def-map-fn update-map
[val] ; normal function parameters
[a d] ; fields from the map I will be using
(update-d
(update-a my-map (+ a val))
(+ d val)))

展开后,它会生成如下所示的函数定义:

(defn update-map
[my-map val]
(let [a (#(:a %) my-map)
d (#(-> % :b :c :d) my-map)
update-a #(assoc % :a %2)
update-d #(assoc-in % [:b :c :d] %2)]
(update-d
(update-a my-map (+ a val))
(+ d val))))

关于我的宏,让我烦恼的一件事是,my-map 函数参数可在函数体内使用,这对程序员来说并不直观。

这是对宏的良好使用,还是我应该使用完全不同的方法(如动态变量绑定(bind))?

最佳答案

你也许可以使用镜头;然后 getter 和 setter 成为可组合函数。看看herehere .

按照第一个链接,您可以按如下方式设置镜头:

; We only need three fns that know the structure of a lens.
(defn lens [focus fmap] {:focus focus :fmap fmap})
(defn view [x {:keys [focus]}] (focus x))
(defn update [x {:keys [fmap]} f] (fmap f x))

; The identity lens.
(defn fapply [f x] (f x))
(def id (lens identity fapply))

; Setting can be easily defined in terms of update.
(defn put [x l value] (update x l (constantly value)))

(-> 3 (view id))
; 3
(-> 3 (update id inc))
; 4
(-> 3 (put id 7))
; 7

; in makes it easy to define lenses based on paths.
(defn in [path]
(lens
(fn [x] (get-in x path))
(fn [f x] (update-in x path f))))

(-> {:value 3} (view (in [:value])))
; 3
(-> {:value 3} (update (in [:value]) inc))
; {:value 4}
(-> {:value 3} (put (in [:value]) 7))
; {:value 7}

您可以从上面看到镜头可以根据您正在使用的数据结构调整为使用获取/设置方法(例如获取/更新)。镜头的真正力量似乎也是您所追求的,那就是您可以组合它们。在同一个例子中,组合函数可以定义如下:

(defn combine [outer inner]
(lens
(fn [x] (-> x (view outer) (view inner)))
(fn [f x] (update x outer #(update % inner f)))))

(defn => [& lenses] (reduce combine lenses))

现在 => 函数可用于组合任意镜头,例如:

(-> {:new {:value 3}} (view (=> (in [:new]) (in [:value]))))
; 3
(-> {:new {:value 3}} (update (=> (in [:new]) (in [:value])) inc))
; {:new {:value 4}}
(-> {:new {:value 3}} (put (=> (in [:new]) (in [:value])) 7))
; {:new {:value 7}}

事实上 (in [:new]) 只是一个函数,这意味着您可以,例如,以各种方式存储和操作它。例如,可以遍历您的嵌套映射结构并创建对应于访问嵌套映射中每个级别的值的镜头函数,然后在最后将这些函数组合在一起以创建您的 getter/setter api。通过此设置,您的镜头可以自动适应架构中的任何变化。

组合镜头的功能还可以让您轻松地与嵌套 map 的节点进行交互。例如,如果您要将节点从原子更改为列表,您可以简单地添加一个新镜头来使用它,如下所示:

(def each (lens seq map))

(-> {:values [3 4 5]} (view (=> (in [:values]) each)))
; (3 4 5)
(-> {:values [3 4 5]} (update (=> (in [:values]) each) inc))
; {:values (4 5 6)}
(-> {:values [3 4 5]} (put (=> (in [:values]) each) 7))
; {:values (7 7 7)}

我强烈建议查看完整的 gist以查看有关您可以使用镜头做什么的更多示例。

关于macros - 使用宏绑定(bind) getter 和 setter,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/24764537/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com