Haskell Language
लेंस
खोज…
परिचय
लेंस , हास्केल के लिए एक पुस्तकालय है जो लेंस, आइसोमोर्फिम्स, सिलवटों, ट्रैवर्सल्स, गेटर्स और सेटर प्रदान करता है, जो मनमाने ढंग से संरचनाओं के क्वेरी और हेरफेर के लिए एक समान इंटरफ़ेस को उजागर करता है, जावा के एक्सेसर और म्यूटेंट अवधारणाओं के विपरीत नहीं।
टिप्पणियों
एक लेंस क्या है?
लेंस (और अन्य प्रकाशिकी) हमें बताया कि कैसे हम हम इसके साथ क्या करना चाहते हैं क्या से कुछ डेटा का उपयोग करना चाहते हैं अलग करने के लिए अनुमति देते हैं। लेंस की अमूर्त धारणा और ठोस कार्यान्वयन के बीच अंतर करना महत्वपूर्ण है। लंबे समय में lens
साथ प्रोग्रामिंग को समझना काफी आसान हो जाता है। लेंस के कई आइसोमॉर्फिक निरूपण हैं इसलिए इस चर्चा के लिए हम किसी भी ठोस कार्यान्वयन चर्चा से बचेंगे और इसके बजाय अवधारणाओं का उच्च-स्तरीय अवलोकन करेंगे।
ध्यान केंद्रित
अमूर्त को समझने में एक महत्वपूर्ण अवधारणा ध्यान केंद्रित करने की धारणा है। महत्वपूर्ण प्रकाशिकी बड़े संदर्भ के बारे में भूल के बिना एक बड़ी डेटा संरचना के विशिष्ट भाग पर ध्यान केंद्रित करती है। उदाहरण के लिए, लेंस _1
एक ट्यूपल के पहले तत्व पर केंद्रित है लेकिन दूसरे क्षेत्र में जो था, उसके बारे में नहीं भूलता है।
एक बार जब हम ध्यान केंद्रित करते हैं, तो हम इस बारे में बात कर सकते हैं कि हमें लेंस के साथ कौन से ऑपरेशन करने की अनुमति है। एक Lens sa
दिया जाता है, जिसे दिए जाने पर टाइप s
का डेटाटाइप एक विशेष पर केंद्रित होता a
, हम या तो कर सकते हैं
- निकालें
a
अतिरिक्त संदर्भ के बारे में भूल से या - बदलें
a
एक नया मान प्रदान करके
ये जाने-माने get
और set
ऑपरेशन के अनुरूप होते हैं, जो आमतौर पर एक लेंस को चिह्नित करने के लिए उपयोग किए जाते हैं।
अन्य प्रकाशिकी
हम इसी तरह से अन्य प्रकाशिकी के बारे में बात कर सकते हैं।
ऑप्टिक | पर केंद्रित... |
---|---|
लेंस | एक उत्पाद का एक हिस्सा |
प्रिज्म | एक राशि का एक हिस्सा |
traversal | डेटा संरचना के शून्य या अधिक भाग |
समाकृतिकता | ... |
प्रत्येक ऑप्टिक एक अलग तरीके से केंद्रित होता है, जैसे कि, इस बात पर निर्भर करता है कि हमारे पास किस प्रकार का ऑप्टिक ऑपरेशन है।
रचना
क्या अधिक है, हम जटिल डेटा एक्सेस निर्दिष्ट करने के लिए हमारे द्वारा अभी तक चर्चा की गई दो प्रकाशिकी में से किसी की रचना कर सकते हैं। हमने जिन चार प्रकार के प्रकाशिकी पर चर्चा की है, वे एक जाली के रूप में हैं, दो प्रकाशिकी को एक साथ संयोजित करने का परिणाम उनकी ऊपरी सीमा है।
उदाहरण के लिए, यदि हम एक लेंस और एक प्रिज्म को एक साथ जोड़ते हैं, तो हमें एक आघात लगता है। इसका कारण यह है कि उनकी (ऊर्ध्वाधर) रचना से, हम पहले एक उत्पाद के एक भाग पर और फिर एक राशि के एक भाग पर ध्यान केंद्रित करते हैं। परिणाम एक ऑप्टिक है जो हमारे डेटा के ठीक शून्य या एक हिस्से पर ध्यान केंद्रित करता है जो ट्रैवर्सल का एक विशेष मामला है। (इसे कभी-कभी एफाइन ट्रैवर्सल भी कहा जाता है)।
हास्केल में
हास्केल में लोकप्रियता का कारण प्रकाशिकी का एक बहुत ही संक्षिप्त प्रतिनिधित्व है। सभी प्रकाशिकी केवल एक निश्चित रूप के कार्य हैं, जिन्हें फ़ंक्शन संरचना का उपयोग करके एक साथ बनाया जा सकता है। इससे एक बहुत ही हल्के वजन का एम्बेडिंग होता है जो आपके कार्यक्रमों में प्रकाशिकी को एकीकृत करना आसान बनाता है। इसके अलावा, एन्कोडिंग के विवरणों के कारण, फ़ंक्शन रचना भी स्वचालित रूप से दो ऑप्टिक्स की ऊपरी सीमा की गणना करती है जो हम लिखते हैं। इसका मतलब है कि हम स्पष्ट कास्टिंग के बिना विभिन्न ऑप्टिक्स के लिए एक ही कॉम्बिनेटर का पुन: उपयोग कर सकते हैं।
लेंस के साथ ट्यूलिप को जोड़ते हुए
मिल रहा
("a", 1) ^. _1 -- returns "a"
("a", 1) ^. _2 -- returns 1
स्थापना
("a", 1) & _1 .~ "b" -- returns ("b", 1)
संशोधित करना
("a", 1) & _2 %~ (+1) -- returns ("a", 2)
both
ट्रैवर्सल
(1, 2) & both *~ 2 -- returns (2, 4)
रिकॉर्ड के लिए लेंस
सरल रिकॉर्ड
{-# LANGUAGE TemplateHaskell #-}
import Control.Lens
data Point = Point {
_x :: Float,
_y :: Float
}
makeLenses ''Point
लेंस x
और y
बनाए गए हैं।
let p = Point 5.0 6.0
p ^. x -- returns 5.0
set x 10 p -- returns Point { _x = 10.0, _y = 6.0 }
p & x +~ 1 -- returns Point { _x = 6.0, _y = 6.0 }
फ़ील्ड नामों को दोहराने के साथ रिकॉर्ड प्रबंधित करना
data Person = Person { _personName :: String }
makeFields ''Person
एक प्रकार के वर्ग का HasName
, Person
लिए लेंस का name
, और Person
को HasName
का एक उदाहरण बनाता है। बाद में रिकॉर्ड को कक्षा में भी जोड़ा जाएगा:
data Entity = Entity { _entityName :: String }
makeFields ''Entity
कार्य करने के लिए makeFields
लिए टेम्पलेट हास्केल एक्सटेंशन आवश्यक है। तकनीकी रूप से, यह पूरी तरह से संभव है कि लेंस को इस तरह से बनाया जाए, जैसे कि हाथ से।
स्टेटफुल लेंस
लेंस ऑपरेटरों के पास उपयोगी वेरिएंट हैं जो राज्य के संदर्भों में काम करते हैं। वे जगह से प्राप्त कर रहे हैं ~
साथ =
ऑपरेटर नाम पर।
(+~) :: Num a => ASetter s t a a -> a -> s -> t
(+=) :: (MonadState s m, Num a) => ASetter' s a -> a -> m ()
नोट: स्टेटस वेरिएंट के प्रकार को बदलने की उम्मीद नहीं है, इसलिए उनके पास
Lens'
याSimple Lens'
हस्ताक्षर हैं।
&
चेन से छुटकारा
यदि लेंस-फुल संचालन को जंजीर बनाने की आवश्यकता है, तो यह अक्सर ऐसा दिखता है:
change :: A -> A
change a = a & lensA %~ operationA
& lensB %~ operationB
& lensC %~ operationC
इस की संबद्धता के लिए धन्यवाद काम करता है &
। स्टेटफुल संस्करण हालांकि स्पष्ट है।
change a = flip execState a $ do
lensA %= operationA
lensB %= operationB
lensC %= operationC
यदि lensX
वास्तव में id
, तो पूरे ऑपरेशन को सीधे modify
करके केवल इसे उठाकर निष्पादित किया जा सकता है।
संरचित राज्य के साथ इंपीरियल कोड
इस उदाहरण की स्थिति को मानते हुए:
data Point = Point { _x :: Float, _y :: Float }
data Entity = Entity { _position :: Point, _direction :: Float }
data World = World { _entities :: [Entity] }
makeLenses ''Point
makeLenses ''Entity
makeLenses ''World
हम उस कोड को लिख सकते हैं जो क्लासिक अनिवार्य भाषाओं से मिलता जुलता है, जबकि अभी भी हमें हास्केल के लाभों का उपयोग करने की अनुमति देता है:
updateWorld :: MonadState World m => m ()
updateWorld = do
-- move the first entity
entities . ix 0 . position . x += 1
-- do some operation on all of them
entities . traversed . position %= \p -> p `pointAdd` ...
-- or only on a subset
entities . traversed . filtered (\e -> e ^. position.x > 100) %= ...
टेम्पलेट हास्केल के बिना लेंस लिखना
टेम्पलेट हास्केल को ध्वस्त करने के लिए, मान लीजिए कि आपके पास है
data Example a = Example { _foo :: Int, _bar :: a }
फिर
makeLenses 'Example
उत्पादन (कम या ज्यादा)
foo :: Lens' (Example a) Int bar :: Lens (Example a) (Example b) a b
वहाँ विशेष रूप से जादुई कुछ भी नहीं है, हालांकि। आप इन्हें स्वयं लिख सकते हैं:
foo :: Lens' (Example a) Int -- :: Functor f => (Int -> f Int) -> (Example a -> f (Example a)) ;; expand the alias foo wrap (Example foo bar) = fmap (\newFoo -> Example newFoo bar) (wrap foo) bar :: Lens (Example a) (Example b) a b -- :: Functor f => (a -> f b) -> (Example a -> f (Example b)) ;; expand the alias bar wrap (Example foo bar) = fmap (\newBar -> Example foo newBar) (wrap bar)
अनिवार्य रूप से, आप wrap
फ़ंक्शन के साथ "अपने लेंस" "फोकस" पर जाना चाहते हैं और फिर "संपूर्ण" प्रकार का पुनर्निर्माण करते हैं।
लेंस और प्रिज्म
एक Lens' sa
मतलब है कि आप हमेशा एक पा सकते हैं a
किसी भी भीतर s
। एक Prism' sa
मतलब है आप कभी कभी मिल सकता है कि कि s
वास्तव में सिर्फ है a
, लेकिन कभी कभी यह कुछ और।
अधिक स्पष्ट होने के लिए, हमारे पास _1 :: Lens' (a, b) a
क्योंकि किसी भी ट्यूपल में हमेशा पहला तत्व होता है। हम _Just :: Prism' (Maybe a) a
क्योंकि कभी-कभी Maybe a
वास्तव में एक है a
में लिपटे मूल्य Just
, लेकिन कभी कभी यह Nothing
।
इस अंतर्ज्ञान के साथ, कुछ मानक कॉम्बिनेटरों को एक दूसरे के समानांतर व्याख्या की जा सकती है
-
view :: Lens' sa -> (s -> a)
"हो जाता है"a
से बाहरs
-
set :: Lens' sa -> (a -> s -> s)
"सेट"a
में स्लॉटs
-
review :: Prism' sa -> (a -> s)
"एहसास" एक है किa
हो सकता है एकs
-
preview :: Prism' sa -> (s -> Maybe a)
"प्रयास" एक बारी करने केs
एक मेंa
।
एक और तरीका है इसके बारे में सोचने के लिए प्रकार का एक मान Lens' sa
यह दर्शाता है कि s
रूप में एक ही संरचना है (r, a)
किसी अज्ञात के लिए r
। दूसरी ओर, Prism' sa
यह दर्शाता है कि s
रूप में एक ही संरचना है Either ra
कुछ के लिए r
। हम इस ज्ञान के साथ उन चार कार्यों को लिख सकते हैं:
-- `Lens' s a` is no longer supplied, instead we just *know* that `s ~ (r, a)` view :: (r, a) -> a view (r, a) = a set :: a -> (r, a) -> (r, a) set a (r, _) = (r, a) -- `Prism' s a` is no longer supplied, instead we just *know* that `s ~ Either r a` review :: a -> Either r a review a = Right a preview :: Either r a -> Maybe a preview (Left _) = Nothing preview (Right a) = Just a
Traversals
एक Traversal' sa
शो है कि s
0-से-अनेक है a
इसके बारे में रों अंदर।
toListOf :: Traversal' s a -> (s -> [a])
किसी भी प्रकार का t
जो Traversable
स्वचालित रूप से वह traverse :: Traversal (ta) a
।
हम एक का उपयोग कर सकते Traversal
सेट करने के लिए या इन सभी से अधिक नक्शा a
मान
> set traverse 1 [1..10]
[1,1,1,1,1,1,1,1,1,1]
> over traverse (+1) [1..10]
[2,3,4,5,6,7,8,9,10,11]
एक f :: Lens' sa
वहाँ वास्तव में एक है का कहना है कि a
के अंदर s
। एक g :: Prism' ab
कहते हैं वहाँ या तो 0 या 1 हैं b
में a
। रचना f . g
हमें एक देता है Traversal' sb
क्योंकि निम्नलिखित f
और फिर g
से पता चलता है कि कैसे वहाँ 0 से 1 हैं b
में रों s
।
लेंस रचना करते हैं
अगर आपके पास f :: Lens' ab
और g :: Lens' bc
तो f . g
एक f
पहला भाग है और फिर g
अनुसरण करके एक Lens' ac
f . g
Lens' ac
। विशेष रूप से:
- लेंस कार्य (वास्तव में वे सिर्फ कार्य हैं) के रूप में रचना
- आप के बारे में सोच तो
view
की कार्यक्षमताLens
, यह डेटा प्रवाह "बाएं से दाएं" की तरह लगता है -इस समारोह रचना के लिए अपने सामान्य अंतर्ज्ञान पीछे की ओर लग सकता है। दूसरी ओर, अगर आपको लगता है कि यह स्वाभाविक महसूस करना चाहिए.
-ओओ भाषाओं में यह कैसे होता है जैसे नोट नहीं।
केवल Lens
साथ Lens
रचना करने से अधिक, (.)
का उपयोग लगभग किसी भी " Lens
-जैसे" प्रकार को एक साथ करने के लिए किया जा सकता है। यह देखना हमेशा आसान नहीं होता है कि परिणाम क्या है क्योंकि प्रकार का पालन करना कठिन हो जाता है, लेकिन आप इसका पता लगाने के लिए lens
चार्ट का उपयोग कर सकते हैं। रचना x . y
पास उस चार्ट में x
और y
दोनों प्रकार के सबसे कम-ऊपरी-प्रकार हैं।
उत्तम दर्जे का लेंस
Lens
es जनरेट करने के लिए मानक makeLenses
फ़ंक्शन के अलावा, Control.Lens.TH
भी makeClassy
फ़ंक्शन प्रदान करता है। makeClassy
का एक ही प्रकार है और अनिवार्य रूप से makeLenses
के समान कार्य करता है, जिसमें एक प्रमुख अंतर है। मानक लेंस और ट्रैवर्सल उत्पन्न करने के अलावा, यदि प्रकार में कोई तर्क नहीं है, तो यह उन सभी डेटाटिप्स का वर्णन करने वाला एक वर्ग भी बनाएगा, जो क्षेत्र के रूप में प्रकार के अधिकारी हों। उदाहरण के लिए
data Foo = Foo { _fooX, _fooY :: Int }
makeClassy ''Foo
बनाएगा
class HasFoo t where
foo :: Simple Lens t Foo
instance HasFoo Foo where foo = id
fooX, fooY :: HasFoo t => Simple Lens t Int
MakeFields के साथ फ़ील्ड
( इस StackOverflow जवाब से कॉपी किया गया उदाहरण)
मान लें कि आपके पास कई अलग-अलग प्रकार के डेटा हैं जो सभी को एक ही नाम के साथ एक लेंस होना चाहिए, इस मामले में capacity
। makeFields
स्लाइस एक ऐसा वर्ग बनाएगा जो बिना नाम के संघर्ष के इसे पूरा करेगा।
{-# LANGUAGE FunctionalDependencies
, MultiParamTypeClasses
, TemplateHaskell
#-}
module Foo
where
import Control.Lens
data Foo
= Foo { fooCapacity :: Int }
deriving (Eq, Show)
$(makeFields ''Foo)
data Bar
= Bar { barCapacity :: Double }
deriving (Eq, Show)
$(makeFields ''Bar)
फिर घिसी में:
*Foo
λ let f = Foo 3
| b = Bar 7
|
b :: Bar
f :: Foo
*Foo
λ fooCapacity f
3
it :: Int
*Foo
λ barCapacity b
7.0
it :: Double
*Foo
λ f ^. capacity
3
it :: Int
*Foo
λ b ^. capacity
7.0
it :: Double
λ :info HasCapacity
class HasCapacity s a | s -> a where
capacity :: Lens' s a
-- Defined at Foo.hs:14:3
instance HasCapacity Foo Int -- Defined at Foo.hs:14:3
instance HasCapacity Bar Double -- Defined at Foo.hs:19:3
यह वास्तव में क्या किया है एक वर्ग घोषित किया जाता है तो HasCapacity sa
है, जहां क्षमता एक है Lens'
से s
को a
( a
एक बार रों जाना जाता है तय हो गई है)। यह क्षेत्र से डेटा प्रकार के (निचली श्रेणी) के नाम को हटाकर "क्षमता" नाम का पता लगाता है; मुझे यह सुखद लगता है कि क्षेत्र के नाम या लेंस के नाम पर अंडरस्कोर का उपयोग न करना, क्योंकि कभी-कभी रिकॉर्ड सिंटैक्स वास्तव में वही होता है जो मैं चाहता हूं। आप लेंस नामों की गणना के लिए कुछ अलग विकल्प रखने के लिए मेक फील्ड्स और विभिन्न लेंसरेल्स का उपयोग कर सकते हैं।
मामले में यह मदद करता है, ghci -ddump-splices Foo.hs का उपयोग करते हुए:
[1 of 1] Compiling Foo ( Foo.hs, interpreted )
Foo.hs:14:3-18: Splicing declarations
makeFields ''Foo
======>
class HasCapacity s a | s -> a where
capacity :: Lens' s a
instance HasCapacity Foo Int where
{-# INLINE capacity #-}
capacity = iso (\ (Foo x_a7fG) -> x_a7fG) Foo
Foo.hs:19:3-18: Splicing declarations
makeFields ''Bar
======>
instance HasCapacity Bar Double where
{-# INLINE capacity #-}
capacity = iso (\ (Bar x_a7ne) -> x_a7ne) Bar
Ok, modules loaded: Foo.
तो पहले ब्याह ने क्लास को HasCapcity
बनाया और फू के लिए एक उदाहरण जोड़ा; दूसरे ने मौजूदा वर्ग का उपयोग किया और बार के लिए एक उदाहरण बनाया।
यदि आप किसी अन्य मॉड्यूल से HasCapcity
वर्ग को आयात करते हैं तो यह भी काम करता है; makeFields
मौजूदा वर्ग में अधिक उदाहरण जोड़ सकता है और आपके प्रकारों को कई मॉड्यूल में फैला सकता है। लेकिन अगर आप इसे फिर से किसी अन्य मॉड्यूल में उपयोग करते हैं, जहाँ आपने कक्षा को आयात नहीं किया है, तो यह एक नया वर्ग (उसी नाम के साथ) बना देगा, और आपके पास दो अलग-अलग ओवरलोडेड क्षमता वाले लेंस होंगे जो संगत नहीं हैं।