assoc-in
на Clojure ви позволява да посочите път през вложена структура от данни, като използвате цели числа и ключови думи и да въведете нова стойност в този път. Той има партньори dissoc-in
, get-in
и update-in
, които премахват елементи, получават ги без премахване или ги променят съответно.
Лещите са специално понятие за двупосочно програмиране, при което указвате връзка между два източника на данни и тази връзка ви позволява да отразявате трансформациите от единия към другия. В Haskell това означава, че можете да създавате лещи или подобни на лещи стойности, които свързват цяла структура от данни с някои от нейните части и след това да ги използвате, за да предавате промени от частите към цялото.
Тук има аналогия. Ако разгледаме използването на assoc-in
, то се пише като
(assoc-in whole path subpart)
и може да придобием известна представа, като мислим за path
като обектив и assoc-in
като комбиниран обектив. По подобен начин можете да пишете (използвайки пакета Haskell lens
)
set lens subpart whole
така че да свържем assoc-in
с set
и path
с lens
. Можем също да допълним таблицата
set assoc-in
view get-in
over update-in
(unneeded) dissoc-in -- this is special because `at` and `over`
-- strictly generalize dissoc-in
Това е начало за приликите, но има и огромно различие. В много отношения lens
е далеч по-генеричен от *-in
семейството на функциите на Clojure. Обикновено това не е проблем за Clojure, тъй като повечето данни на Clojure се съхраняват във вложени структури, съставени от списъци и речници. Haskell използва много повече потребителски типове много свободно и неговата система от типове отразява информация за тях. Лещите обобщават семейството от функции *-in
, защото работят безпроблемно в тази много по-сложна област.
Първо, нека вградим типове Clojure в Haskell и да напишем семейството от функции *-in
.
type Dict a = Map String a
data Clj
= CljVal -- Dynamically typed Clojure value,
-- not an array or dictionary
| CljAry [Clj] -- Array of Clojure types
| CljDict (Dict Clj) -- Dictionary of Clojure types
makePrisms ''Clj
Сега можем да използваме set
като assoc-in
почти директно.
(assoc-in whole [1 :foo :bar 3] part)
set ( _CljAry . ix 1
. _CljDict . ix "foo"
. _CljDict . ix "bar"
. _CljAry . ix 3
) part whole
Това донякъде очевидно има много повече синтактичен шум, но обозначава по-висока степен на яснота за това какво означава „пътят“ към даден тип данни, по-специално показва дали се спускаме в масив или речник. Бихме могли, ако искаме, да елиминираме част от този допълнителен шум чрез инстанциране на Clj
в типовия клас на Haskell Ixed
, но едва ли си струва на този етап.
Вместо това трябва да се отбележи, че assoc-in
се прилага за много специфичен вид спускане на данни. Той е по-общ от типовете, които изложих по-горе, поради динамичното въвеждане на Clojure и претоварването на IFn
, но много подобна фиксирана структура като тази може да бъде вградена в Haskell с малко допълнителни усилия.
Обективите обаче могат да стигнат много по-далеч и го правят с по-голяма безопасност на типа. Например, примерът по-горе всъщност не е истинска „Ленза“, а вместо това „Призма“ или „Обхождане“, което позволява на системата от типове да идентифицира статично възможността да не успее да направи това преминаване. Това ще ни принуди да мислим за такива условия на грешка (дори ако решим да ги игнорираме).
Важното е, че това означава, че можем да сме сигурни, когато имаме истинска леща, че спускането на типа данни не може да се провали – такъв вид гаранция е невъзможно да се направи в Clojure.
Можем да дефинираме персонализирани типове данни и да направим персонализирани лещи, които се спускат в тях по безопасен за типовете начин.
data Point =
Point { _latitude :: Double
, _longitude :: Double
, _meta :: Map String String }
deriving Show
makeLenses ''Point
> let p0 = Point 0 0
> let p1 = set latitude 3 p0
> view latitude p1
3.0
> view longitude p1
0.0
> let p2 = set (meta . ix "foo") "bar" p1
> preview (meta . ix "bar") p2
Nothing
> preview (meta . ix "foo") p2
Just "bar"
Можем също така да обобщим за лещи (всъщност Traversals), които са насочени към множество подобни подчасти наведнъж
dimensions :: Lens Point Double
> let p3 = over dimensions (+ 10) p0
> get latitude p3
10.0
> get longitude p3
10.0
> toListOf dimensions p3
[10.0, 10.0]
Или дори да се насочите към симулирани подчасти, които всъщност не съществуват, но все пак формират еквивалентно описание на нашите данни
eulerAnglePhi :: Lens Point Double
eulerAngleTheta :: Lens Point Double
eulerAnglePsi :: Lens Point Double
Най-общо казано, Lenses обобщават вида базирано на пътя взаимодействие между цели стойности и подчасти от стойности, които фамилията функции Clojure *-in
абстрахира. Можете да направите много повече в Haskell, защото Haskell има много по-развито понятие за типове и лещи, като първокласни обекти, широко обобщаващи понятията за получаване и настройка, които са просто представени с *-in
функциите.
person
J. Abrahamson
schedule
22.01.2014