Haskell 中的镜头与 Clojure 中的解构有何异同?

What are the similarities and differences between a lens in Haskell and destructuring in Clojure?

假设:

我们可以看到可以在Haskell中创建一个lens like this:

{-# LANGUAGE TemplateHaskell #-}

import Control.Lens

initialState :: Game
initialState = Game
    { _score = 0
    , _units =
        [ Unit
            { _health = 10
            , _position = Point { _x = 3.5, _y = 7.0 }
            }
        ]
    }

health :: Lens' Unit Int
health = lens _health (\unit v -> unit { _health = v })

它的目的是从 game 数据结构中获取 health 值。

我们可以在 Clojure 中进行解构 like this

user=> (def book {:name "SICP" :details {:pages 657 :isbn-10 "0262011530"}})
#'user/book

user=> (let [{name :name {pages :pages isbn-10 :isbn-10} :details} book]
     (println "name:" name "pages:" pages "isbn-10:" isbn-10))
name: SICP pages: 657 isbn-10: 0262011530

目的是从嵌套结构中获取名称、页面等的嵌套值。

在这两种情况下,您都想出了一种机制来从嵌套结构中可重用地检索值。

我的问题是:Haskell 中的镜头与 Clojure 中的解构有何异同?

您的 Clojure 代码最接近于在 Haskell 中这样表达:

book :: Book
book = Book {name = "SICP", details = Details {pages = 657, isbn_10: "0262011530"}}

main = do
  let Book {name = name, details = Details {pages = pages, isbn_10 = isbn_10}} = book
  putStrLn $ "name:" ++ name ++ "pages:" ++ (show pages) ++ "isbn-10:" ++ isbn-10

这里我们使用了 Haskell 的模式匹配,它基本上是 Clojure 解构绑定的一种更通用的形式,以获取记录的内容。这与镜头无关。

现在让我们用镜头看一下您的 Haskell 代码。此代码具有三种记录类型,Game、Unit 和 Point,并且这些类型各自具有一组自动定义的函数来访问其成员而无需模式匹配。这些函数称为 _score_health_x 等(就像成员名称一样)。它们分别采用 GameUnitPoint 并产生各自成员的值。如果必须手动定义这些函数(它们不需要,因为 Haskell 会自动为所有记录类型定义此类函数),它看起来像这样:

_health (Unit {health = h}) = h

到目前为止,这仍然与镜头无关。

代码中的镜头是health。它包含函数 _health(如前所述,它接受一个 Unit 并产生它的生命值)和函数 (\unit v -> unit { _health = v }),它接受一个单位和一个值并产生一个新单位,其生命值已设置为该值。换句话说,镜头包含获取和设置单元健康状况的代码。镜头库现在包含各种函数,您可以使用这些函数来组合和使用此类镜头。

所以回答你的问题:镜头和解构之间没有相似之处。