gpt4 book ai didi

haskell - 在操作不可变数据结构时,Clojure assoc-in 和 Haskell 的镜头有什么区别?

转载 作者:行者123 更新时间:2023-12-02 11:36:42 24 4
gpt4 key购买 nike

我需要操作和修改深度嵌套的不可变集合( map 和列表),并且我想更好地理解不同的方法。这两个库或多或少解决了相同的问题,对吧?它们有什么不同,一种方法比另一种方法更适合哪种类型的问题?

Clojure's assoc-in
Haskell's lens

最佳答案

Clojure 的 assoc-in允许您使用整数和关键字指定通过嵌套数据结构的路径,并在该路径处引入新值。有合作伙伴dissoc-in , get-in , 和 update-in删除元素,在不删除的情况下获取它们,或分别修改它们。

镜头是双向编程的一种特殊概念,您可以在其中指定两个数据源之间的链接,并且该链接允许您反射(reflect)从一个数据源到另一个数据源的转换。在 Haskell 中,这意味着您可以构建透镜或类似透镜的值,将整个数据结构连接到它的某些部分,然后使用它们将更改从部分传输到整体。

这里有一个类比。如果我们看一下 assoc-in 的用法它写得像

(assoc-in whole path subpart)

我们可能会通过思考 path 获得一些见解。作为镜头和 assoc-in作为镜头组合器。以类似的方式编写(使用 Haskell lens 包)
set lens subpart whole

以便我们连接 assoc-insetpathlens .我们也可以完成表格
set          assoc-in
view get-in
over update-in
(unneeded) dissoc-in -- this is special because `at` and `over`
-- strictly generalize dissoc-in

这是相似之处的开始,但也有巨大的不同。在许多方面, lens*-in 更通用Clojure 函数家族是。通常这对 Clojure 来说不是问题,因为大多数 Clojure 数据都存储在由列表和字典组成的嵌套结构中。 Haskell 非常自由地使用了更多的自定义类型,并且它的类型系统反射(reflect)了关于它们的信息。镜头概括 *-in函数族,因为它们可以在更复杂的域上顺利运行。

首先,让我们在 Haskell 中嵌入 Clojure 类型并编写 *-in函数族。
type Dict a = Map String a

data Clj
= CljVal -- Dynamically typed Clojure value,
-- not an array or dictionary
| CljAry [Clj] -- Array of Clojure types
| CljDict (Dict Clj) -- Dictionary of Clojure types

makePrisms ''Clj

现在我们可以使用 setassoc-in几乎直接。
(assoc-in whole [1 :foo :bar 3] part)

set ( _CljAry . ix 1
. _CljDict . ix "foo"
. _CljDict . ix "bar"
. _CljAry . ix 3
) part whole

这在某种程度上显然有更多的语法噪音,但它表示数据类型的“路径”意味着什么的更高程度的明确性,特别是它表示我们是下降到数组还是字典。如果我们愿意,我们可以通过实例化 Clj 来消除一些额外的噪音。在 Haskell 类型类中 Ixed ,但在这一点上几乎不值得。

相反,要指出的是 assoc-in适用于一种非常特殊的数据下降。由于 Clojure 的动态类型和 IFn 的重载,它比我上面列出的类型更通用。 , 但是一个非常相似的固定结构可以嵌入到 Haskell 中,而不需要进一步的努力。

但是,镜头可以走得更远,并且具有更高的类型安全性。例如,上面的示例实际上不是真正的“镜头”,而是“棱镜”或“遍历”,它允许类型系统静态识别无法进行遍历的可能性。它将迫使我们考虑这样的错误条件(即使我们选择忽略它们)。

重要的是,这意味着当我们有一个真正的镜头时,我们可以确定数据类型下降不会失败——这种保证在 Clojure 中是不可能做出的。

我们可以定义自定义数据类型,并以类型安全的方式制作自定义镜头。
data Point = 
Point { _latitude :: Double
, _longitude :: Double
, _meta :: Map String String }
deriving Show

makeLenses ''Point

> let p0 = Point 0 0
> let p1 = set latitude 3 p0
> view latitude p1
3.0
> view longitude p1
0.0
> let p2 = set (meta . ix "foo") "bar" p1
> preview (meta . ix "bar") p2
Nothing
> preview (meta . ix "foo") p2
Just "bar"

我们还可以推广到镜头(真正的遍历),它同时针对多个相似的子部分
dimensions :: Lens Point Double

> let p3 = over dimensions (+ 10) p0
> get latitude p3
10.0
> get longitude p3
10.0
> toListOf dimensions p3
[10.0, 10.0]

甚至针对实际上不存在但仍形成我们数据的等效描述的模拟子部分
eulerAnglePhi   :: Lens Point Double
eulerAngleTheta :: Lens Point Double
eulerAnglePsi :: Lens Point Double

从广义上讲,Lenses 概括了 Clojure *-in 整个值和值的子部分之间基于路径的交互类型。函数族抽象。你可以在 Haskell 中做更多的事情,因为 Haskell 有一个更成熟的类型概念,而 Lenses 作为一流的对象,广泛概括了 *-in 中简单呈现的获取和设置的概念。功能。

关于haskell - 在操作不可变数据结构时,Clojure assoc-in 和 Haskell 的镜头有什么区别?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/21291712/

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