镜片和拉链有什么区别?

haw*_*eye 25 haskell functional-programming clojure zipper haskell-lens

这是在Haskell中使用拉链的一个例子:

data Tree a = Fork (Tree a) (Tree a) | Leaf a
data Cxt a = Top | L (Cxt a) (Tree a) | R (Tree a) (Cxt a)
type Loc a = (Tree a, Cxt a)

left :: Loc a -> Loc a
left (Fork l r, c) = (l, L c r)

right :: Loc a -> Loc a
right (Fork l r, c) = (r, R l c)

top :: Tree a -> Loc a 
top t = (t, Top)

up :: Loc a -> Loc a
up (t, L c r) = (Fork t r, c)
up (t, R l c) = (Fork l t, c)

upmost :: Loc a -> Loc a
upmost l@(t, Top) = l
upmost l = upmost (up l)

modify :: Loc a -> (Tree a -> Tree a) -> Loc a
modify (t, c) f = (f t, c)
Run Code Online (Sandbox Code Playgroud)

这是在Clojure中使用拉链的一个例子:

(use 'clojure.zip)
(require '[clojure.zip :as z])

user> (def z [[1 2 3] [4 [5 6] 7] [8 9]])
#'user/z

user> (def zp (zipper vector? seq (fn [_ c] c) z))
#'user/zp

user> zp
[[[1 2 3] [4 [5 6] 7] [8 9]] nil]

user> (-> zp down)
[[1 2 3] {:l [], :pnodes [[[1 2 3] [4 [5 6] 7] [8 9]]], :ppath nil, :r ([4 [5 6] 7] [8 9])}]

user> (first (-> zp down))
[1 2 3]
Run Code Online (Sandbox Code Playgroud)

这是在Haskell中使用镜头的示例:

data Person = P { name :: String 
                , addr :: Address 
                }
data Address = A { street :: String
                 , city :: String
                 , postcode :: String 
                 }

setPostcode :: String -> Person -> Person
setPostcode pc p = p { addr = addr p { postcode = pc }}
Run Code Online (Sandbox Code Playgroud)

这是在Clojure中使用镜头的一个例子.

(use 'lens)

(defrecord Address [street city postcode])
(defrecord Person [name age address])
(defrecord User [uid username identity password])

(def -postcode (mklens :postcode))
(def -city (mklens :city))
(def -street (mklens :street))
(def -address (mklens :address))
(def -age (mklens :age))
(def -name (mklens :name))
(def -uid (mklens :uid))
(def -username (mklens :username))
(def -identity (mklens :identity))
(def -password (mklens :password))

(-get -postcode home)

(-set -postcode home 500)
Run Code Online (Sandbox Code Playgroud)

现在看来透镜和拉链都是遍历嵌套数据结构的功能方式.

我的问题是:镜片和拉链之间有什么区别?是否适合特定用例?

cgr*_*and 27

拉链类似于游标:它们允许以有序的方式遍历树木.他们通常的操作是up,down,left,rightedit.(名称可能因impl而异)

镜头是某种通用键(如"关联数据结构的键").结构不需要订购.他们通常的操作是fetchputback和非常相似的getassoc.(名称可能因impl而异)

所以当你看到拉链非常关注层次结构(向上/向下)和顺序(左/右),而镜头只是关注一个数据(因此名称),甚至可能是一个投影(这是一个东西)这在原始结构中并不存在.

例如,在我正在进行的关于Enliven的工作中,我有一些镜头可以让我专注于HTML文档中的单个类或样式属性.


J. *_*son 11

拉链是其展开的类型到其一个数据类型的一个变型本地上下文和它的盘区在所有方向上.在Zipper上,您可以实现高效的动作本地更新.

镜头是数据类型的特定组件的一流检查.它们专注于数据结构的0,1或许多子部分.值得注意的是,你在Haskell中使用镜头的例子实际上不是镜头 - 它不是一流的.

构建一个专注于拉链某些部分的镜头是完全合理的.例如,比您的示例更简单的拉链是Cons列表拉链

data Cons a = Empty | Cons a (Cons a)

data ConsZ a = ConsZ { lefts :: Cons a; here :: a; rights :: Cons a }

zip :: Cons a -> Maybe (ConsZ a)
zip Empty = Nothing
zip (Cons a as) = ConsZ Empty a as

unzip :: ConsZ a -> Cons a
unzip (ConsZ Empty a as) = Cons a as
unzip (ConsZ (Cons l ls) a as) = unzip (ConsZ ls) l (Cons a as)
Run Code Online (Sandbox Code Playgroud)

我们可以逐步修改此结构,向左或向右移动焦点

moveRight :: ConsZ a -> Maybe (ConsZ a)
moveRight (ConsZ _ _ Empty) = Nothing
moveRight (ConsZ ls x (Cons a as)) =  ConsZ (Cons x ls) a as
Run Code Online (Sandbox Code Playgroud)

并修改当前的本地点

modify :: (a -> a) -> ConsZ a -> ConsZ a
modify f (ConsZ ls x rs) = ConsZ ls (f x) rs
Run Code Online (Sandbox Code Playgroud)

我们还可以制造可以进入拉链结构的每个部分的镜片

type Lens s a = forall f . Functor f => (a -> f a) -> (s -> f s)

_lefts :: Lens (ConsZ a) a
_lenfs inj (ConsZ ls x rs) = (\ls -> ConsZ ls' x rs) <$> inj ls

_here :: Lens (ConsZ a) a
_here inj (ConsZ ls x rs) = (\x' -> ConsZ ls x' rs) <$> inj x
Run Code Online (Sandbox Code Playgroud)

甚至用它们来有效地构建我们的拉链动作

over :: ((a -> Identity a) -> s -> Identity s) -> (a -> a) -> (s -> s)
over l f s = runIdentity (l (Identity . f) s)

modify = over _here
Run Code Online (Sandbox Code Playgroud)

然而,最终,镜头始终是对数据结构中特定点的第一类访问.它们可以组合在一个类型中给出"运动"的幻觉,但是如果你真的想要它那么你应该使拉链变换并使用真正的拉链类型.