2014-02-28 32 views
23

这是在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 [email protected](t, Top) = l 
upmost l = upmost (up l) 

modify :: Loc a -> (Tree a -> Tree a) -> Loc a 
modify (t, c) f = (f t, c) 

这是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] 

这是在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 }} 

这是一个在Clojure中使用Lens的例子。

(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) 

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

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

+2

你可以在拉链中移动吗?一般来说拉链有“左”,“上”等原始形式;您通常无法将镜头稍微向左移动。但他们密切相关。 – drquicksilver

+0

镜头在概念上是一个可以任意深入数据结构的getter/setter对(它实际上甚至比这更一般)。拉链是一种特定类型的数据结构,您可以(至少)左右移动/上下移动。 –

回答

24

拉链类似于游标:它们允许遍历树木订购的方式。他们通常的操作是updownleftrightedit。 (名称可能取决于IMPL会发生变化)

镜头有某种广义键(如在“关联数据结构的键”)。该结构不需要订购。他们通常的操作是fetchputback,并且非常类似于getassoc。 (名称可能因impl而异)

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

例如,在我正在进行的工作的Enliven,我的镜头,让我专注于HTML文档中的单个类或样式属性。

7

拉链是其展开的类型到其本地上下文及其程度在所有方向上的数据类型的变体。在拉链顶上你可以实现高效的运动本地更新

镜片是一个数据类型的一个特定部件的第一类检查。它们专注于数据结构的0,1或多个子部分。值得注意的是,你在Haskell中的镜头实际上并不是镜头 - 它不是头等舱。

这是完全合理的,构建一个镜头侧重于拉链的某些部分。举例来说,一个更简单的拉链比你的例子是一个缺点列表拉链

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) 

我们可以逐步改变这种结构,向右移动

moveRight :: ConsZ a -> Maybe (ConsZ a) 
moveRight (ConsZ _ _ Empty) = Nothing 
moveRight (ConsZ ls x (Cons a as)) = ConsZ (Cons x ls) a as 

留下的焦点或和修改当前的局部点

modify :: (a -> a) -> ConsZ a -> ConsZ a 
modify f (ConsZ ls x rs) = ConsZ ls (f x) rs 

我们还可以建立其访问拉链结构的每一部分镜头

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 

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

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

modify = over _here 

然而,归根结底,一个镜头总是一流访问特定点的数据结构。它们可以组成一个类型的“运动”幻觉,但如果你真的想这样做,那么你应该做拉链变换,并使用真正的拉链类型。