मानक समाधान एक अस्तित्व में मात्रात्मक डेटा प्रकार बनाना है। परिणाम
{-# LANGUAGE ExistentialQuantification #-}
data Exists1 = forall a . (Show a) => Exists1 a
instance Show Exists1 where
showsPrec _ (Exists1 x) = shows x
somethingPrintable1 :: Int -> Exists1
somethingPrintable1 x = Exists1 x
की तरह कुछ अब हो सकता है, एक स्वतंत्र रूप से show (somethingPrintable 42)
उपयोग कर सकते हैं। Exists1
newtype
नहीं हो सकता है, मुझे लगता है कि ऐसा इसलिए है क्योंकि एक छिपे हुए संदर्भ शब्दकोश में show
के विशेष कार्यान्वयन को पार करना आवश्यक है।
प्रकार सुरक्षित वैक्टर के लिए, एक आगे बढ़ सके fromList1
कार्यान्वयन के लिए उसी तरह:
{-# LANGUAGE GADTs #-}
data Zero
data Succ n
data Vec a n where
Nil :: Vec a Zero
Cons :: a -> Vec a n -> Vec a (Succ n)
data Exists1 f where
Exists1 :: f a -> Exists1 f
fromList1 :: [a] -> Exists1 (Vec a)
fromList1 [] = Exists1 Nil
fromList1 (x:xs) = case fromList1 xs of
Exists1 r -> Exists1 $ Cons x r
यह अच्छी तरह से काम करता है, लेकिन मुख्य दोष यह है मैं देख रहा हूँ अतिरिक्त निर्माता है। fromList1
पर प्रत्येक कॉल के परिणामस्वरूप कन्स्ट्रक्टर के एक आवेदन में परिणाम होता है, जिसे तुरंत डीकोनस्ट्रक्टेड किया जाता है। पहले की तरह, newtype
Exists1
के लिए संभव नहीं है, लेकिन मुझे लगता है कि किसी भी प्रकार की कक्षा की बाधाओं के बिना संकलक इसे अनुमति दे सकता है।
मैंने रैंक-एन निरंतरता के आधार पर एक और समाधान बनाया। इसे अतिरिक्त कन्स्ट्रक्टर की आवश्यकता नहीं है, लेकिन मुझे यकीन नहीं है, अगर अतिरिक्त फ़ंक्शन एप्लिकेशन समान ओवरहेड नहीं जोड़ता है। पहले मामले में, समाधान होगा:
{-# LANGUAGE Rank2Types #-}
somethingPrintable2 :: Int -> ((forall a . (Show a) => a -> r) -> r)
somethingPrintable2 x = \c -> c x
अब एक somethingPrintable 42 show
का उपयोग परिणाम प्राप्त करने के हैं।
और, Vec
डेटा प्रकार के लिए:
{-# LANGUAGE RankNTypes, GADTs #-}
fromList2 :: [a] -> ((forall n . Vec a n -> r) -> r)
fromList2 [] c = c Nil
fromList2 (x:xs) c = fromList2 xs (c . Cons x)
-- Or wrapped as a newtype
-- (this is where we need RankN instead of just Rank2):
newtype Exists3 f r = Exists3 { unexists3 :: ((forall a . f a -> r) -> r) }
fromList3 :: [a] -> Exists3 (Vec a) r
fromList3 [] = Exists3 (\c -> c Nil)
fromList3 (x:xs) = Exists3 (\c -> unexists3 (fromList3 xs) (c . Cons x))
इस में कुछ सहायक कार्यों का उपयोग कर में थोड़ा और अधिक पठनीय बनाया जा सकता है:
-- | A helper function for creating existential values.
exists3 :: f x -> Exists3 f r
exists3 x = Exists3 (\c -> c x)
{-# INLINE exists3 #-}
-- | A helper function to mimic function application.
(?$) :: (forall a . f a -> r) -> Exists3 f r -> r
(?$) f x = unexists3 x f
{-# INLINE (?$) #-}
fromList3 :: [a] -> Exists3 (Vec a) r
fromList3 [] = exists3 Nil
fromList3 (x:xs) = (exists3 . Cons x) ?$ fromList3 xs
मुख्य नुकसान मैं यहाँ देख रहे हैं:
- अतिरिक्त फ़ंक्शन एप्लिकेशन के साथ संभावित ओवरहेड (मुझे नहीं पता कि संकलक कितना चुन सकता है इसे imize)।
- कम पठनीय कोड (कम से कम लोगों के लिए निरंतरता के लिए उपयोग नहीं किया जाता है)।