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)
现在看来透镜和拉链都是遍历嵌套数据结构的功能方式.
我的问题是:镜片和拉链之间有什么区别?是否适合特定用例?
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)
然而,最终,镜头始终是对数据结构中特定点的第一类访问.它们可以组合在一个类型中给出"运动"的幻觉,但是如果你真的想要它那么你应该使拉链变换并使用真正的拉链类型.