खोज…


परिचय

लेंस , हास्केल के लिए एक पुस्तकालय है जो लेंस, आइसोमोर्फिम्स, सिलवटों, ट्रैवर्सल्स, गेटर्स और सेटर प्रदान करता है, जो मनमाने ढंग से संरचनाओं के क्वेरी और हेरफेर के लिए एक समान इंटरफ़ेस को उजागर करता है, जावा के एक्सेसर और म्यूटेंट अवधारणाओं के विपरीत नहीं।

टिप्पणियों

एक लेंस क्या है?

लेंस (और अन्य प्रकाशिकी) हमें बताया कि कैसे हम हम इसके साथ क्या करना चाहते हैं क्या से कुछ डेटा का उपयोग करना चाहते हैं अलग करने के लिए अनुमति देते हैं। लेंस की अमूर्त धारणा और ठोस कार्यान्वयन के बीच अंतर करना महत्वपूर्ण है। लंबे समय में lens साथ प्रोग्रामिंग को समझना काफी आसान हो जाता है। लेंस के कई आइसोमॉर्फिक निरूपण हैं इसलिए इस चर्चा के लिए हम किसी भी ठोस कार्यान्वयन चर्चा से बचेंगे और इसके बजाय अवधारणाओं का उच्च-स्तरीय अवलोकन करेंगे।

ध्यान केंद्रित

अमूर्त को समझने में एक महत्वपूर्ण अवधारणा ध्यान केंद्रित करने की धारणा है। महत्वपूर्ण प्रकाशिकी बड़े संदर्भ के बारे में भूल के बिना एक बड़ी डेटा संरचना के विशिष्ट भाग पर ध्यान केंद्रित करती है। उदाहरण के लिए, लेंस _1 एक ट्यूपल के पहले तत्व पर केंद्रित है लेकिन दूसरे क्षेत्र में जो था, उसके बारे में नहीं भूलता है।

एक बार जब हम ध्यान केंद्रित करते हैं, तो हम इस बारे में बात कर सकते हैं कि हमें लेंस के साथ कौन से ऑपरेशन करने की अनुमति है। एक Lens sa दिया जाता है, जिसे दिए जाने पर टाइप s का डेटाटाइप एक विशेष पर केंद्रित होता a , हम या तो कर सकते हैं

  1. निकालें a अतिरिक्त संदर्भ के बारे में भूल से या
  2. बदलें 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 जवाब से कॉपी किया गया उदाहरण)

मान लें कि आपके पास कई अलग-अलग प्रकार के डेटा हैं जो सभी को एक ही नाम के साथ एक लेंस होना चाहिए, इस मामले में capacitymakeFields स्लाइस एक ऐसा वर्ग बनाएगा जो बिना नाम के संघर्ष के इसे पूरा करेगा।

{-# 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 मौजूदा वर्ग में अधिक उदाहरण जोड़ सकता है और आपके प्रकारों को कई मॉड्यूल में फैला सकता है। लेकिन अगर आप इसे फिर से किसी अन्य मॉड्यूल में उपयोग करते हैं, जहाँ आपने कक्षा को आयात नहीं किया है, तो यह एक नया वर्ग (उसी नाम के साथ) बना देगा, और आपके पास दो अलग-अलग ओवरलोडेड क्षमता वाले लेंस होंगे जो संगत नहीं हैं।



Modified text is an extract of the original Stack Overflow Documentation
के तहत लाइसेंस प्राप्त है CC BY-SA 3.0
से संबद्ध नहीं है Stack Overflow