12

我注意到,而在我的追求瘦函数式编程,有情况下,当参数列表开始使用嵌套不变的数据结构时变得过大。这是因为在更新对象状态时,您还需要更新数据结构中的所有父节点。请注意,在这里我将“update”表示为“用适当的更改返回新的不可变对象”。管理更新嵌套的不可变的数据结构,函数式语言

例如我发现自己写(Clojure的例子)的一种功能是:

(defn update-object-in-world [world country city building object property value] 
    (update-country-in-world world 
    (update-city-in-country country 
     (update-building-in-city building 
     (update-object-in-building object property value))))) 

这一切都更新一个简单的属性是很丑陋,但除了调用者必须集合所有的参数!

与一般函数式语言不可改变的数据结构时,这必须是一个相当普遍的要求,所以是有一个很好的模式或办法来避免这一点,我应该使用呢?

+2

您可以拼合数据:分别存储世界,国家,城市等。然后,如果您必须更新一个,请在平面结构中更新它。通过密钥将数据链接在一起,以便在需要时再将它们放在一起。不过,我们现在正在重新创建关系数据库。 – 2010-06-29 16:59:52

回答

2

有,我知道的两种方法:

收集在某种物体是方便以绕过多个参数。 例子:

; world is a nested hash, the rest are keys 
(defstruct location :world :country :city :building) 
(defstruct attribute :object :property) 

(defn do-update[location attribute value] 
    (let [{:keys [world country city building]} location 
     {:keys [object property]} attribute ] 
    (update-in world [country city building object property] value))) 

这使你到调用者需要关心(位置和属性),这可能是不够公平,如果这些参数不经常改变两个参数。

另一种方法是用-X宏,它的代码体将使用的变量:

(defmacro with-location [location & body] ; run body in location context 
    (concat 
    (list 'let ['{:keys [world country city building] :as location} `~location]) 
    `([email protected]))) 

Example use: 
(with-location location (println city)) 

那么无论身体呢,它对世界/国家/城市/建筑的设置它可以使用“预装配”location参数将整个事件传递给另一个函数。现在,随着与定位宏的实际工作:

更新

+0

非常有用,谢谢!所以看起来,即使你不能完全摆脱参数的泛滥,你至少可以使用宏或HoF来使它看起来好多了...... – mikera 2010-06-29 15:37:47

+0

是的,将它们包装成一个方便的形式几乎是最好你可以做。您在面向对象语言中使用“配置对象”进行的分类,这些“配置对象”仅用于封装参数。 – 2010-06-29 15:48:28

7

尝试

(update-in 
    world 
    [country city building] 
    (update-object-in-building object property value)) 
+0

谢谢 - 这是我以前从未见过的非常有用的功能!然而,它仍然给我们带来了很多参数....猜测有没有办法围绕 – mikera 2010-06-29 13:06:39

+1

看看联想 – nickik 2010-06-29 14:46:37

5

一个典型的通用解决这个问题是什么叫做"zipper" data structure。有许多变化,但其基本思想很简单:给定一个嵌套的数据结构,在遍历它时将其分开,以便在每一步中都有一个“当前”元素和一个代表如何重构数据结构的其余部分位于当前元素的“上方”。一个拉链可以被认为是一个“游标”,它可以通过一个不可变的数据结构进行移动,代之以取代部分,只重建它所需的部分。

在列表的琐碎情况下,所述片段是刚刚列表的前面的元素,存储在相反的顺序,并且遍历只是移动一个清单到另一个所述第一元件。

在二进制树的非平凡但仍然简单情况下,每个片段由一个值和一个子树,识别为向右或向左的。将拉链移动“向左”包括向片段列表中添加当前元素的值和右侧子元素,使左侧子元素成为新的当前元素。移动“向右”的作用类似,通过将当前元素与片段列表上的第一个值和子树结合来完成向上移动。

虽然拉链的基本思想非常普遍,但为特定数据结构构建拉链通常需要一些专用位(如自定义遍历或构造操作)以供通用拉链实现使用。

original paper describing zippers(警告,PDF)给出了OCaml中的示例代码,用于实现通过树存储具有显式路径的片段。不出所料,在Haskell的拉链上也可以找到大量材料。作为构建明确的路径和片段列表的替代方案,可以实施拉链in Scheme using continuations。最后,似乎甚至有一个tree-oriented zipper provided by Clojure