महान शक्ति newtypes

न्यूटाइप एक विशेष डेटा प्रकार की घोषणा है। ऐसा है कि इसमें केवल एक कंस्ट्रक्टर और फ़ील्ड शामिल है।

newtype Foo a = Bar a newtype Id = MkId Word 


ठेठ नौसिखिया सवाल


डेटा प्रकार डेटा से क्या अंतर है?

 data Foo a = Bar a data Id = MkId Word 

न्यूटाइप की मुख्य विशिष्टता यह है कि इसमें एक ही हिस्से के रूप में इसके एकमात्र क्षेत्र शामिल हैं। अधिक सटीक रूप से, यह मूल प्रकार के स्तर से भिन्न होता है, लेकिन स्मृति में समान प्रतिनिधित्व होता है, और इसकी सख्ती से गणना की जाती है (आलसी नहीं)।
संक्षेप में - उनकी प्रस्तुति के कारण न्यूटाइप अधिक प्रभावी हैं

हां, इसका मतलब मेरे लिए कुछ भी नहीं है ... मैं डेटा का उपयोग करूंगा
नहीं, ठीक है, अंत में, आप हमेशा एक्सटेंशन -funpack- सख्त फ़ील्ड को सक्षम कर सकते हैं :) सख्त (आलसी नहीं) फ़ील्ड के लिए या सीधे निर्दिष्ट करें

 data Id = MkId !Word 

फिर भी, न्यूटाइप की शक्ति कम्प्यूटेशनल दक्षता तक सीमित नहीं है। वे बहुत मजबूत हैं!

3 न्यूटाइप भूमिकाएं




कार्यान्वयन छिपाना


 module Data.Id (Id()) where newtype Id = MkId Word 

newtype मूल से अलग है, आंतरिक रूप से सिर्फ वर्ड
लेकिन हम मॉड्यूल के बाहर एमकेआईडी कंस्ट्रक्टर को छिपाते हैं

क्रियान्वयन कार्यान्वयन


 {-# LANGUAGE GeneralizedNewtypeDeriving #-} newtype Id = MkId Word deriving (Num, Eq) 

यद्यपि यह Haskell2010 मानक में ऐसा नहीं है, जेनेरिक newTypes के आउटपुट का विस्तार करके, आप आंतरिक क्षेत्र के व्यवहार के समान ही newtype के व्यवहार का अनुमान लगा सकते हैं। हमारे मामले में, Eq Id और Num Id का व्यवहार Eq Word और Num Word जैसा ही है।

परिष्कृत प्रजनन ( डेरिविंगिया ) के विस्तार के माध्यम से बहुत कुछ प्राप्त किया जा सकता है, लेकिन बाद में उस पर और अधिक।

पसंद का कार्यान्वयन


अपने स्वयं के निर्माता के बावजूद, कुछ मामलों में आप अपने आंतरिक प्रतिनिधित्व का उपयोग कर सकते हैं।

कार्य


पूर्णांकों की एक सूची है। सूची में केवल एक पास में अधिकतम और कुल राशि का पता लगाएं।
और फोल्ड और फोल्ड पैकेज का उपयोग न करें।

विशिष्ट उत्तर


बेशक, गुना ! :)

 foldr :: Foldable t => (a -> b -> b) -> b -> ta -> b {- -- instance Foldable [] foldr :: (a -> b -> b) -> b -> [a] -> b -} 

और, अंतिम कार्य इस तरह वर्णित है:

 aggregate :: [Integer] -> (Maybe Integer, Integer) aggregate = foldr (\el (m, s) -> (Just el `max` m, el + s)) (Nothing, 0) {- ghci> aggregate [1, 2, 3, 4] (Just 4, 10) -} 

यदि आप बारीकी से देखते हैं, तो आप दोनों तरफ समान संचालन देख सकते हैं: बस एल `अधिकतम` मी और एल + एस । दोनों मामलों में, मैपिंग और बाइनरी ऑपरेशन। और खाली तत्व कुछ भी नहीं है और 0 हैं

हाँ, ये मोनॉयड हैं!

अधिक विस्तार में मोनोइड और सेमीग्रुप
एक अर्धवृत्त एक साहचर्य बाइनरी ऑपरेशन की एक संपत्ति है

 x ⋄ (y ⋄ z) == (x ⋄ y) ⋄ z 

एक मोनॉइड एक साहचर्य संचालन की संपत्ति है (यानी एक अर्धसमूह)

 x ⋄ (y ⋄ z) == (x ⋄ y) ⋄ z 

जिसके पास एक खाली तत्व है जो किसी भी तत्व को दाईं ओर या बाईं ओर नहीं बदलता है

 x ⋄ empty == x == empty ⋄ x 


दोनों अधिकतम और (+) सहयोगी हैं, दोनों में खाली तत्व हैं - कुछ भी नहीं और 0

और कनवल्शन के साथ मोनोड्स के मानचित्रण का संयोजन फोल्डेबल है!

तह अधिक विस्तृत
तह की परिभाषा याद करें:

 class Foldable t where foldMap :: (Monoid m) => (a -> m) -> ta -> m ... 


आइए फोल्डिंग व्यवहार को अधिकतम और (+) पर लागू करें । हम वर्ड मोनॉइड के एक से अधिक कार्यान्वयन को व्यवस्थित नहीं कर सकते हैं। यह न्यूटाइप पसंद के कार्यान्वयन का लाभ उठाने का समय है!

 {-# LANGUAGE GeneralizedNewtypeDeriving #-} -- already in Data.Semigroup & Data.Monoid newtype Sum a = Sum {getSum :: a} deriving (Num, Eq, Ord) instance (Num a, Ord a) => Semigroup (Sum a) where (<>) = (+) instance (Num a, Ord a) => Monoid (Sum a) where mempty = Sum 0 newtype Max a = Max {getMax :: a} deriving (Num, Eq, Ord) instance (Num a, Ord a) => Semigroup (Max a) where (<>) = max 

टिप्पणी करना आवश्यक है।

तथ्य यह है कि अधिकतम डेटा प्रकार के लिए एक मोनॉयड होने के लिए, हमें खाली तत्व के अस्तित्व के लिए एक न्यूनतम तत्व की आवश्यकता होती है। इसलिए, केवल एक सीमित मैक्स एक मोनॉइड हो सकता है।

सैद्धांतिक रूप से अधिकतम तत्व monoid सही
 newtype Max a = Max a instance Ord a => Semigroup (Max a) instance Bounded a => Monoid (Max a) 


तो किसी तरह हमें अपने डेटा प्रकार को परिवर्तित करना होगा ताकि एक खाली तत्व दिखाई दे और हम जमावट का उपयोग कर सकें।

 -- already in Prelude data Maybe a = Nothing | Just a instance Semigroup a => Semigroup (Maybe a) where Nothing <> b = b b <> Nothing = b (Just a) <> (Just b) = Just (a <> b) instance Semigroup a => Monoid (Maybe a) where mempty = Nothing -- ------ instance Functor Maybe where fmap _ Nothing = Nothing fmap f (Just b) = Just (fb) 

संयुग्म शायद तत्व एक अर्धवृत्त को एक मोनोइड में बदल देता है!

जीएचसी के हाल के संस्करणों में प्रतिबंधों का उदारीकरण
वापस जीएचसी 8.2 में, प्रकार की बाधा में एक मोनोड की आवश्यकता थी

 instance Monoid a => Monoid (Maybe a) 

जिसका मतलब है कि हमें एक और नए प्रकार की आवश्यकता है:

 -- already in Data.Semigroup & Data.Monoid newtype Option a = Option {getOption :: Maybe a} deriving (Eq, Ord, Semigroup) instance (Ord a, Semigroup a) => Monoid (Option a) where mempty = Option Nothing 

और यह पहले से ही जीएचसी 8.4 में बहुत सरल है, जहां केवल एक प्रकार का प्रतिबंध आवश्यक है, और यहां तक ​​कि एक विकल्प प्रकार बनाने की भी आवश्यकता नहीं है।

 instance Semigroup a => Monoid (Maybe a) 


फोल्डिंग रिस्पांस


ठीक है, अब कोड को विश्वसनीयता और तीर का उपयोग करके अपडेट करें।
हम याद करते हैं कि (।) बस एक कार्यात्मक रचना है:

  (.) :: (b -> c) -> (a -> b) -> a -> c f . g = \x -> f (gx) 

और याद रखें कि फैंप एक फ़नकार है:

 fmap :: Functor f => (a -> b) -> fa -> fb 

और शायद इसके लिए इसके कार्यान्वयन को थोड़ा अधिक बताया गया है।

अधिक विस्तृत तीर
तीर कुछ कार्यों के गुण हैं जो आपको ब्लॉक आरेख में उनके साथ काम करने की अनुमति देते हैं।
अधिक विवरण यहां पाया जा सकता है: तीर: गणना के लिए एक सामान्य इंटरफ़ेस
हमारे मामले में, हम एरो फ़ंक्शन का उपयोग करते हैं
वह है

 instance Arrow (->) 

हम कार्यों का उपयोग करेंगे:

 (***) :: Arrow a => abc -> ab' c' -> a (b, b') (c, c') (&&&) :: Arrow a => abc -> abc' -> ab (c, c') 

हमारे मामले के लिए
 abc == (->) bc == b -> c 

और, तदनुसार, हमारे कार्यों के हस्ताक्षर को घटाया जाता है:

 (***) :: (b -> c) -> (b' -> c') -> ((b, b') -> (c, c')) (&&&) :: (b -> c) -> (b -> c') -> (b -> (c, c')) 

या काफी सरल शब्दों में, एक फ़ंक्शन (***) एक फ़ंक्शन में दो तर्कों (और एक आउटपुट प्रकार) को एक फ़ंक्शन में इनपुट और आउटपुट पर, तर्कों की एक जोड़ी के काम के साथ जोड़ती है, क्रमशः आउटपुट प्रकार की एक जोड़ी।

फ़ंक्शन (&&&) एक स्ट्राइप्ड-डाउन संस्करण (***) है , जहां दो फ़ंक्शन के इनपुट तर्कों का प्रकार समान है, और इनपुट में हमारे पास एक तर्क नहीं, बल्कि एक तर्क है।

कुल, एकीकृत फ़ंक्शन ने प्रपत्र प्राप्त कर लिया है:

 import Data.Semigroup import Data.Monoid import Control.Arrow aggregate :: [Integer] -> (Maybe Integer, Integer) aggregate = (fmap getMax *** getSum) . (foldMap (Just . Max &&& Sum)) {- -- for GHC 8.2 aggregate = (fmap getMax . getOption *** getSum) . (foldMap (Option . Just . Max &&& Sum)) -} 

यह बहुत संक्षेप में निकला!

लेकिन, यह अभी भी नेस्टेड प्रकारों से डेटा को लपेटने और उलटने के लिए थकाऊ है!
आप इसे और कम कर सकते हैं, और एक संसाधन-मुक्त मजबूर रूपांतरण हमारी मदद करेगा!

सुरक्षित, संसाधन-मुक्त मजबूर रूपांतरण और प्रकार की भूमिकाएँ


Unsafe.Coerce पैकेज से एक फ़ंक्शन है - असुरक्षित

 import Unsafe.Coerce(unsafeCoerce) unsafeCoerce :: a -> b 

फ़ंक्शन जबरन असुरक्षित टाइप करता है: से बी तक
वास्तव में, फ़ंक्शन जादू है, यह संकलक को इस चरण के परिणामों को ध्यान में रखे बिना टाइप बी के डेटा पर विचार करने के लिए कहता है।

इसका उपयोग नेस्टेड प्रकारों को परिवर्तित करने के लिए किया जा सकता है, लेकिन आपको बहुत सावधान रहने की आवश्यकता है।

2014 में, न्यूटाइप के साथ एक क्रांति हुई, अर्थात्, एक सुरक्षित, संसाधन-कम मजबूर रूपांतरण!

 import Data.Coerce(coerce) coerce :: Coercible ab => a -> b 

इस फीचर ने newtype के साथ काम करने में एक नया युग खोला है।

Coercible Forced कनवर्टर मेमोरी में समान संरचना वाले प्रकारों के साथ काम करता है। यह एक प्रकार के वर्ग की तरह दिखता है, लेकिन वास्तव में जीएचसी प्रकारों को संकलित समय पर परिवर्तित करता है और उदाहरणों को अपने आप परिभाषित करना संभव नहीं है।
Data.Coerce.coerce फ़ंक्शन आपको संसाधनों के बिना प्रकार परिवर्तित करने की अनुमति देता है, लेकिन इसके लिए हमें टाइप कंस्ट्रक्टर्स तक पहुंच की आवश्यकता है।

अब हमारे कार्य को सरल बनाएँ:

 import Data.Semigroup import Data.Monoid import Control.Arrow import Data.Coerce aggregate :: [Integer] -> (Maybe Integer, Integer) aggregate = coerce . (foldMap (Just . Max &&& Sum)) -- coerce :: (Maybe (Max Integer), Sum Integer) -> (Maybe Integer, Integer) 

हमने घोंसले के प्रकारों को खींचने की दिनचर्या से परहेज किया, हमने केवल एक फ़ंक्शन के साथ संसाधनों को बर्बाद किए बिना ऐसा किया।

नेस्टेड डेटा प्रकार की भूमिकाएँ


मोटे फ़ंक्शन के साथ , हम किसी भी नेस्टेड प्रकार के रूपांतरण को मजबूर कर सकते हैं।
लेकिन क्या इस सुविधा का व्यापक रूप से उपयोग करना आवश्यक है?

 -- already in Data.Ord -- Down a - reversed order newtype Down a = Down a deriving (Eq, Show) instance Ord a => Ord (Down a) where compare (Down x) (Down y) = y `compare` x import Data.List(sort) -- Sorted data Sorted a = Sorted [a] deriving (Show, Eq, Ord) fromList2Sorted :: Ord a => [a] -> Sorted a fromList2Sorted = Sorted . sort -- minimum: O(1) ! minView :: Sorted a -> Maybe a minView (Sorted []) = Nothing minView (Sorted (a : _)) = Just a 

शब्दार्थ, छाँटे गए (नीचे) से छाँटे जाने के लिए परिवर्तित करना बेतुका है।
हालाँकि, आप कोशिश कर सकते हैं:

 ghci> let h = fromList2Sorted [1,2,3] :: Sorted Int ghci> let hDown = fromList2Sorted $ fmap Down [1,2,3] :: Sorted (Down Int) ghci> minView h Just (Down 1) ghci> minView (coerce h :: Sorted (Down Int)) Just (Down 1) ghci> minView hDown Just (Down 3) 

सबकुछ ठीक हो जाएगा, लेकिन सही जवाब जस्ट (डाउन 3) है
अर्थात्, गलत व्यवहार को काटने के लिए, प्रकार की भूमिकाएँ शुरू की गईं।

 {-# LANGUAGE RoleAnnotations #-} type role Sorted nominal 

आइए अब कोशिश करते हैं:

 ghci> minView (coerce h :: Sorted (Down Int)) error: Couldn't match type 'Int' with 'Down Int' arising from a use of 'coerce' 

काफी बेहतर है!

कुल में 3 भूमिकाएँ ( प्रकार भूमिका ) हैं:

  • अभ्यावेदन - यदि समान प्रतिनिधित्व
  • नाममात्र - बिल्कुल उसी प्रकार का होना चाहिए
  • प्रेत - वास्तविक सामग्री से स्वतंत्र। किसी चीज के बराबर

ज्यादातर मामलों में, संकलक स्मार्ट प्रकार की भूमिका को प्रकट करने के लिए पर्याप्त है, लेकिन इसकी मदद की जा सकती है।

परिशोधित व्युत्पन्न डेरिवेविया व्यवहार


डेरिवेविया भाषा के विस्तार के लिए धन्यवाद, न्यूटाइप की वितरण भूमिका में सुधार हुआ है।

जीएचसी 8.6 के साथ शुरू, जिसे हाल ही में जारी किया गया था, यह नया विस्तार दिखाई दिया है।

 {-# LANGUAGE DerivingVia #-} newtype Id = MkId Word deriving (Semigroup, Monoid) via Max Word 

जैसा कि आप देख सकते हैं, प्रकार व्यवहार स्वचालित रूप से कैसे उत्पादन के लिए शोधन के कारण अनुमान है।
DerivingVia किसी भी प्रकार के लिए लागू किया जा सकता है जो Coercible का समर्थन करता है और सबसे महत्वपूर्ण बात - पूरी तरह से संसाधन खपत के बिना!

इससे भी अधिक, DerivingVia को न केवल न्यूटाइप के लिए , बल्कि किसी भी आइसोमॉर्फिक प्रकार पर भी लागू किया जा सकता है यदि वे जेनरिक जेनरिक और कोयरसबल मजबूर रूपांतरण का समर्थन करते हैं।

निष्कर्ष


प्रकार न्यूटाइप एक शक्तिशाली बल है जो कोड को सरल और बेहतर बनाता है, दिनचर्या को समाप्त करता है और संसाधन खपत को कम करता है।

मूल अनुवाद : द ग्रेट पावर ऑफ़ न्यूटिप्स (हिरोमी इशी)

पी एस मुझे लगता है, इस लेख के बाद, एक साल पहले [मेरे लेख से नहीं] से अधिक प्रकाशित, नए प्रकार के बारे में हास्केल में न्यूटाइप का जादू थोड़ा स्पष्ट हो जाएगा!

Source: https://habr.com/ru/post/hi425405/


All Articles