В чем разница между линзами и молниями?
Это пример использования застежки-молнии в 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.
(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)
Теперь кажется, что оба объектива и молнии являются функциональными способами прохождения вложенных структур данных.
Мой вопрос: Каковы различия между объективами и молниями? Подходит ли он к конкретному варианту использования?
Ответы
Ответ 1
Молнии сродни курсорам: они позволяют перемещаться по деревьям упорядоченным образом. Их обычные операции: up
, down
, left
, right
и edit
. (имена могут различаться в зависимости от имплантата)
Объективы - это своего рода обобщенные ключи (как в "ключах ассоциативной структуры данных" ). Структуру не нужно заказывать. Их обычные операции fetch
и putback
и очень похожи на get
и assoc
. (имена могут различаться в зависимости от имплантата)
Итак, как вы видите, молнии очень обеспокоены иерархией (вверх/вниз) и порядком (влево/вправо), в то время как объективы просто фокусируются (отсюда и название) на части данных, что может быть даже проекцией ( это то, чего не существовало само по себе в исходной структуре).
Например, в моей текущей работе над Enliven у меня есть объективы, которые позволяют мне сосредоточиться на одном атрибуте класса или стиля в документе HTML.
Ответ 2
Молнии - это вариант типа данных, который разворачивает тип в его локальный контекст и его экстенты во всех направлениях. На вершине 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)
Мы можем постепенно изменять эту структуру, перемещая фокус влево или вправо
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
В конечном счете, однако, объектив всегда является первым классом доступа к определенной точке в структуре данных. Они могут быть составлены, что дает иллюзию "движения" в типе, но если вы действительно этого хотите, тогда вам нужно сделать преобразование молнии и использовать настоящий тип застежки-молнии.