Rank2Types का उद्देश्य क्या है?


110

मैं वास्तव में हास्केल में कुशल नहीं हूं, इसलिए यह एक बहुत आसान सवाल हो सकता है।

रैंक 2Types किस भाषा की सीमा को हल करते हैं? हास्केल में कार्य पहले से ही बहुरूपी तर्कों का समर्थन नहीं करते हैं?


यह मूल रूप से एचएम प्रकार प्रणाली से पॉलीमोर्फिक लैम्ब्डा कैलकुलस उर्फ ​​का अपग्रेड है। λ2 / सिस्टम एफ। ध्यान रखें कि λ2 में अनिर्दिष्ट प्रकार का आक्रमण।
पोसैट

जवाबों:


116

हास्केल में कार्य पहले से ही बहुरूपी तर्कों का समर्थन नहीं करते हैं?

वे करते हैं, लेकिन केवल रैंक 1. इसका मतलब यह है कि जब आप एक फ़ंक्शन लिख सकते हैं जो इस एक्सटेंशन के बिना विभिन्न प्रकार के तर्क लेता है, तो आप एक फ़ंक्शन नहीं लिख सकते हैं जो एक ही आह्वान में विभिन्न प्रकार के तर्क का उपयोग करता है।

उदाहरण के लिए निम्नलिखित फ़ंक्शन को इस एक्सटेंशन के बिना टाइप नहीं किया जा सकता है क्योंकि gइसका परिभाषा में विभिन्न तर्क प्रकारों के साथ उपयोग किया जाता है f:

f g = g 1 + g "lala"

ध्यान दें कि किसी अन्य फ़ंक्शन के तर्क के रूप में एक पॉलीमॉर्फिक फ़ंक्शन को पास करना पूरी तरह से संभव है। तो ऐसा कुछ map id ["a","b","c"]पूरी तरह से कानूनी है। लेकिन फ़ंक्शन केवल इसे मोनोमोर्फिक के रूप में उपयोग कर सकता है। उदाहरण में mapउपयोग करता है idजैसे कि यह टाइप किया गया था String -> String। और निश्चित रूप से आप इसके बजाय दिए गए प्रकार का एक सरल मोनोमोर्फिक फ़ंक्शन भी पास कर सकते हैं id। बिना रेंक 2 टार्ट के किसी फ़ंक्शन के लिए यह आवश्यक नहीं है कि उसके तर्क में एक पॉलीमॉर्फिक फ़ंक्शन होना चाहिए और इस तरह इसे पॉलीमॉर्फिक फ़ंक्शन के रूप में उपयोग करने का कोई तरीका भी नहीं है।


5
मेरे उत्तर को जोड़ने वाले कुछ शब्दों को इसमें जोड़ने के लिए: हास्केल फ़ंक्शन पर विचार करें f' g x y = g x + g y। इसका अनुमानित रैंक -1 प्रकार है forall a r. Num r => (a -> r) -> a -> a -> r। चूंकि forall aफ़ंक्शन एरो के बाहर है, इसलिए कॉल करने वाले को पहले एक प्रकार चुनना होगा a; यदि वे उठाते हैं Int, तो हम प्राप्त करते हैं f' :: forall r. Num r => (Int -> r) -> Int -> Int -> r, और अब हमने gतर्क तय कर लिया है Int, इसलिए इसे ले सकते हैं, लेकिन नहीं String। यदि हम सक्षम RankNTypesकरते हैं तो हम f'टाइप के साथ एनोटेट कर सकते हैं forall b c r. Num r => (forall a. a -> r) -> b -> c -> r। हालांकि, इसका उपयोग नहीं gकिया जा सकता है - क्या होगा ?
लुइस कैसिलास

166

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

लेकिन मूल रूप से, मोटे तौर पर विचार यह है कि बहुरूपी प्रकारों के पास वास्तव में वह a -> bरूप नहीं होता है जैसा वे हास्केल में करते हैं; वास्तव में, वे इस तरह दिखते हैं, हमेशा स्पष्ट मात्रात्मक के साथ:

id :: a.a  a
id = Λtx:t.x

यदि आपको "∀" प्रतीक नहीं पता है, तो इसे "सभी के लिए" के रूप में पढ़ा जाता है; ∀x.dog(x)का अर्थ है "सभी एक्स के लिए, एक्स एक कुत्ता है।" "" "प्रकार के मापदंडों पर अमूर्त करने के लिए उपयोग किया जाने वाला कैपिटल लैम्ब्डा है; दूसरी पंक्ति क्या कहती है कि आईडी एक प्रकार्य है जो एक प्रकार लेता है t, और फिर एक फ़ंक्शन देता है जो उस प्रकार से पैराट्राइज्ड होता है।

आप देखते हैं, सिस्टम एफ में, आप अभी उस तरह से एक फ़ंक्शन लागू नहीं कर सकते हैं id; पहले आपको λ-function को टाइप करने के लिए need-function को एक प्रकार से लागू करने की आवश्यकता होती है, जिसे आप किसी मान पर लागू करते हैं। उदाहरण के लिए:

tx:t.x) Int 5 = x:Int.x) 5
                  = 5

स्टैंडर्ड हास्केल (यानी हास्केल 98 और 2010) इनमें से किसी भी प्रकार के क्वांटिफायर, कैपिटल लैम्ब्डा और टाइप एप्लिकेशन के न होने से आपके लिए सरल है, लेकिन दृश्यों के पीछे जीएचसी उन्हें डालता है जब यह संकलन के लिए कार्यक्रम का विश्लेषण करता है। (यह सब संकलन-समय का सामान है, मेरा मानना ​​है कि कोई रनटाइम ओवरहेड नहीं है।)

लेकिन हास्केल के इस से निपटने का अर्थ है कि यह मानता है कि "appears" कभी भी किसी फ़ंक्शन ("→") प्रकार की बाईं शाखा पर नहीं दिखाई देता है। Rank2Typesऔर RankNTypesउन प्रतिबंधों को बंद करें और आपको हस्केल के डिफ़ॉल्ट नियमों को ओवरराइड करने की अनुमति दें जहां सम्मिलित करना है forall

तुमने ऐसा क्यों करना चाहोगे? क्योंकि पूर्ण, अप्रतिबंधित सिस्टम एफ हेला शक्तिशाली है, और यह बहुत अच्छा सामान कर सकता है। उदाहरण के लिए, उच्च-श्रेणी के प्रकारों का उपयोग करके टाइप छिपाना और प्रतिरूपकता को लागू किया जा सकता है। उदाहरण के लिए निम्न रैंक -1 प्रकार के एक सादे पुराने कार्य को लें (दृश्य सेट करने के लिए):

f :: r.∀a.((a  r)  a  r)  r

उपयोग करने के लिए f, कॉलर को पहले यह चुनना होगा कि किस प्रकार का उपयोग करना है rऔर aफिर परिणामी प्रकार के एक तर्क की आपूर्ति करें। तो आप चुन सकते हैं r = Intऔर a = String:

f Int String :: ((String  Int)  String  Int)  Int

लेकिन अब इसकी तुलना निम्न-रैंक प्रकार से करें:

f' :: r.(∀a.(a  r)  a  r)  r

इस प्रकार का एक कार्य कैसे करता है? खैर, इसका उपयोग करने के लिए, पहले आप यह निर्दिष्ट करें कि किस प्रकार का उपयोग करना है r। हम कहें Int:

f' Int :: (∀a.(a  Int)  a  Int)  Int

लेकिन अब ∀aहै अंदर समारोह तीर, तो आप किस प्रकार के लिए उपयोग करने के लिए नहीं चुन सकते a; आपको f' Intउपयुक्त प्रकार के Λ-फ़ंक्शन पर आवेदन करना होगा । इसका मतलब यह है कि किस प्रकार का उपयोग करना है , इसके लिए कॉल करने वाले के कार्यान्वयन को f'प्राप्त होता aहैf' । उच्च-श्रेणी के प्रकारों के बिना, कॉलर हमेशा प्रकारों को चुनता है।

यह किसके लिए उपयोगी है? वास्तव में, कई चीजों के लिए, लेकिन एक विचार यह है कि आप इसका उपयोग ऑब्जेक्ट-ओरिएंटेड प्रोग्रामिंग जैसी चीजों को मॉडल करने के लिए कर सकते हैं, जहां "ऑब्जेक्ट्स" कुछ छिपे हुए डेटा को कुछ तरीकों के साथ बंडल करते हैं जो छिपे हुए डेटा पर काम करते हैं। उदाहरण के लिए, दो तरीकों वाली एक वस्तु - एक जो एक रिटर्न देती है Intऔर दूसरी जो रिटर्न करती है String, उसे इस प्रकार से लागू किया जा सकता है:

myObject :: r.(∀a.(a  Int, a -> String)  a  r)  r

यह कैसे काम करता है? ऑब्जेक्ट को एक फ़ंक्शन के रूप में लागू किया जाता है जिसमें छिपे हुए प्रकार के कुछ आंतरिक डेटा होते हैं a। वास्तव में ऑब्जेक्ट का उपयोग करने के लिए, इसके ग्राहक एक "कॉलबैक" फ़ंक्शन में पास होते हैं जो ऑब्जेक्ट दो तरीकों से कॉल करेगा। उदाहरण के लिए:

myObject String a. λ(length, name):(a  Int, a  String). λobjData:a. name objData)

यहाँ हम मूल रूप से, ऑब्जेक्ट की दूसरी विधि को लागू करते हैं, जिसका प्रकार a → Stringअज्ञात के लिए है a। खैर, myObjectग्राहकों के लिए अज्ञात ; लेकिन इन ग्राहकों को पता है, हस्ताक्षर से, कि वे दोनों कार्यों में से किसी एक को लागू करने में सक्षम होंगे, और या तो एक Intया एक हस्ताक्षर प्राप्त करेंगे String

वास्तविक हास्केल उदाहरण के लिए, नीचे वह कोड है जो मैंने खुद को पढ़ाते समय लिखा था RankNTypes। यह एक प्रकार को लागू करता है जिसे बुलाया जाता है ShowBoxजो अपने Showवर्ग उदाहरण के साथ कुछ छिपे हुए प्रकार के मूल्य को एक साथ बंडल करता है । ध्यान दें कि नीचे के उदाहरण में, मैं एक सूची बनाता हूं ShowBoxजिसका पहला तत्व एक संख्या से बनाया गया था, और दूसरा एक स्ट्रिंग से। चूंकि प्रकार उच्च-रैंक प्रकारों का उपयोग करके छिपाए जाते हैं, यह प्रकार की जांच का उल्लंघन नहीं करता है।

{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ImpredicativeTypes #-}

type ShowBox = forall b. (forall a. Show a => a -> b) -> b

mkShowBox :: Show a => a -> ShowBox
mkShowBox x = \k -> k x

-- | This is the key function for using a 'ShowBox'.  You pass in
-- a function @k@ that will be applied to the contents of the 
-- ShowBox.  But you don't pick the type of @k@'s argument--the 
-- ShowBox does.  However, it's restricted to picking a type that
-- implements @Show@, so you know that whatever type it picks, you
-- can use the 'show' function.
runShowBox :: forall b. (forall a. Show a => a -> b) -> ShowBox -> b
-- Expanded type:
--
--     runShowBox 
--         :: forall b. (forall a. Show a => a -> b) 
--                   -> (forall b. (forall a. Show a => a -> b) -> b)
--                   -> b
--
runShowBox k box = box k


example :: [ShowBox] 
-- example :: [ShowBox] expands to this:
--
--     example :: [forall b. (forall a. Show a => a -> b) -> b]
--
-- Without the annotation the compiler infers the following, which
-- breaks in the definition of 'result' below:
--
--     example :: forall b. [(forall a. Show a => a -> b) -> b]
--
example = [mkShowBox 5, mkShowBox "foo"]

result :: [String]
result = map (runShowBox show) example

पुनश्च: किसी को भी यह पढ़कर आश्चर्य होता है कि ExistentialTypesजीएचसी का उपयोग कैसे होता है forall, मेरा मानना ​​है कि इसका कारण यह है कि यह इस तरह की तकनीक का उपयोग पर्दे के पीछे कर रहा है।


2
बहुत विस्तृत जवाब के लिए धन्यवाद! (जो, संयोग से, अंत में मुझे उचित प्रकार के सिद्धांत और सिस्टम एफ को सीखने के लिए भी प्रेरित किया)
अलेक्जेंडर दिमित्रोव

5
यदि आपके पास existsकीवर्ड था , तो आप एक अस्तित्वगत प्रकार (उदाहरण के लिए) को परिभाषित कर सकते हैं data Any = Any (exists a. a), जहां Any :: (exists a. a) -> Any। ≡xP (x) → Q ∀ (PxP (x)) → Q का उपयोग करके, हम यह निष्कर्ष निकाल सकते हैं कि Anyएक प्रकार भी हो सकता है forall a. a -> Anyऔर यही वह जगह है जहाँ से forallकीवर्ड आता है। मेरा मानना ​​है कि जीएचसी द्वारा कार्यान्वित अस्तित्वगत प्रकार केवल सामान्य डेटा प्रकार हैं जो सभी आवश्यक टाइपकास्ट शब्दकोशों को भी ले जाते हैं (मैं इसे वापस करने के लिए संदर्भ नहीं पा सका, क्षमा करें)।
विटस

2
@ विटस: जीएचसी मौजूदगी टाइपकास्ट शब्दकोशों में बंधी नहीं है। आपके पास हो सकता है data ApplyBox r = forall a. ApplyBox (a -> r) a; जब आप पैटर्न के लिए मैच ApplyBox f x, आपको मिल f :: h -> rऔर x :: hएक "छिपा हुआ," प्रतिबंधित प्रकार के लिए h। अगर मैं सही समझता हूं, तो टाइपकास्ट डिक्शनरी केस का अनुवाद कुछ इस तरह से किया जाता है: data ShowBox = forall a. Show a => ShowBox aजिसका अनुवाद कुछ इस तरह किया जाता है data ShowBox' = forall a. ShowBox' (ShowDict' a) a; instance Show ShowBox' where show (ShowBox' dict val) = show' dict val; show' :: ShowDict a -> a -> String
लुइस कैसिलस

यह एक महान जवाब है मुझे कुछ समय बिताना होगा। मुझे लगता है कि मैं भी अमूर्त C # जेनेरिक प्रदान करने के लिए उपयोग किया जाता हूं, इसलिए मैं सिद्धांत को समझने के बजाय इसके लिए बहुत कुछ ले रहा था।
एंड्री शेकिन

@ सैकुंडिम: ठीक है, "सभी आवश्यक टाइपकेल्सी शब्दकोशों" का अर्थ यह भी हो सकता है कि यदि आपको किसी भी प्रकार की आवश्यकता नहीं है :) मेरा कहना यह था कि GHC सबसे अधिक संभावित प्रकारों (यानी आपके द्वारा सुझाए गए परिवर्तन - )xP (x) ~ .r। (PxP (x) → r) → r) के माध्यम से अस्तित्वगत प्रकारों को एनकोड नहीं करता है।
विटस

47

लुइस कैसिलस का जवाब बहुत अच्छी जानकारी देता है कि रैंक 2 प्रकार का क्या मतलब है, लेकिन मैं सिर्फ एक बिंदु पर विस्तार करूंगा जो उन्होंने कवर नहीं किया था। बहुरूपी होने के लिए एक तर्क की आवश्यकता होती है जो इसे कई प्रकारों के साथ उपयोग करने की अनुमति नहीं देता है; यह यह भी बताता है कि वह फ़ंक्शन अपने तर्क (नों) के साथ क्या कर सकता है और यह कैसे अपना परिणाम दे सकता है। यानी यह कॉलर को कम लचीलापन देता है। आप ऐसा क्यों करना चाहते हो? मैं एक साधारण उदाहरण से शुरू करता हूँ:

मान लीजिए हमारे पास एक डेटा प्रकार है

data Country = BigEnemy | MediumEnemy | PunyEnemy | TradePartner | Ally | BestAlly

और हम एक फ़ंक्शन लिखना चाहते हैं

f g = launchMissilesAt $ g [BigEnemy, MediumEnemy, PunyEnemy]

यह एक ऐसा फ़ंक्शन लेता है जो उस सूची के तत्वों में से एक को चुनना चाहिए जो उस पर दिया गया है और IOउस लक्ष्य पर मिसाइलों को लॉन्च करने वाली एक कार्रवाई लौटाता है । हम fएक सरल प्रकार दे सकते हैं :

f :: ([Country] -> Country) -> IO ()

समस्या यह है कि हम गलती से भाग सकते हैं

f (\_ -> BestAlly)

और फिर हम बड़ी मुसीबत में पड़ गए! देते हुए fएक रैंक 1 बहुरूपी प्रकार

f :: ([a] -> a) -> IO ()

हम मदद नहीं करते हैं, क्योंकि aजब हम कॉल करते हैं f, तो हम उस प्रकार का चयन करते हैं , और हम इसे फिर से Countryउपयोग करते हैं और अपने दुर्भावनापूर्ण उपयोग \_ -> BestAllyकरते हैं। समाधान रैंक 2 प्रकार का उपयोग करना है:

f :: (forall a . [a] -> a) -> IO ()

अब हम जिस समारोह से गुजरते हैं, उसमें बहुरूपता होना आवश्यक है, इसलिए \_ -> BestAllyचेक टाइप नहीं करेंगे! वास्तव में, यह दिए गए सूची में किसी तत्व को वापस नहीं करने वाला कोई फ़ंक्शन टाइपकास्ट नहीं होगा (हालांकि कुछ फ़ंक्शन जो अनंत छोरों में जाते हैं या त्रुटियों का उत्पादन करते हैं और इसलिए कभी भी ऐसा नहीं करेंगे)।

उपरोक्त, निश्चित रूप से वंचित है, लेकिन इस तकनीक पर एक भिन्नता है जो कि STमोनाड को सुरक्षित बनाने के लिए महत्वपूर्ण है ।


18

उच्च-श्रेणी के प्रकार उतने विदेशी नहीं हैं जितने कि अन्य उत्तर दिए गए हैं। मानो या न मानो, कई ऑब्जेक्ट-ओरिएंटेड भाषाएँ (जावा और C # सहित!) उन्हें सुविधा देती हैं। (ज़ाहिर है, उन समुदायों में कोई भी उन्हें डरावना-लगने वाले नाम "उच्च-श्रेणी के प्रकार" से नहीं जानता है।)

मैं जो उदाहरण देने जा रहा हूं वह विज़िटर पैटर्न का एक पाठ्यपुस्तक कार्यान्वयन है, जिसका उपयोग मैं अपने दैनिक कार्य में हर समय करता हूं। यह उत्तर विज़िटर पैटर्न के परिचय के रूप में नहीं है; वह ज्ञान अन्यत्र आसानी से उपलब्ध है

इस कठिन काल्पनिक एचआर एप्लिकेशन में, हम उन कर्मचारियों पर काम करना चाहते हैं जो पूर्णकालिक स्थायी कर्मचारी या अस्थायी ठेकेदार हो सकते हैं। आगंतुक पैटर्न का मेरा पसंदीदा संस्करण (और वास्तव में वह जो प्रासंगिक है RankNTypes) आगंतुक के वापसी प्रकार को मापता है।

interface IEmployeeVisitor<T>
{
    T Visit(PermanentEmployee e);
    T Visit(Contractor c);
}

class XmlVisitor : IEmployeeVisitor<string> { /* ... */ }
class PaymentCalculator : IEmployeeVisitor<int> { /* ... */ }

मुद्दा यह है कि विभिन्न रिटर्न प्रकारों वाले सभी आगंतुक एक ही डेटा पर काम कर सकते हैं। इसका मतलब IEmployeeहै कि क्या Tहोना चाहिए के रूप में कोई राय व्यक्त करना चाहिए।

interface IEmployee
{
    T Accept<T>(IEmployeeVisitor<T> v);
}
class PermanentEmployee : IEmployee
{
    // ...
    public T Accept<T>(IEmployeeVisitor<T> v)
    {
        return v.Visit(this);
    }
}
class Contractor : IEmployee
{
    // ...
    public T Accept<T>(IEmployeeVisitor<T> v)
    {
        return v.Visit(this);
    }
}

मैं आपका ध्यान प्रकारों की ओर आकर्षित करना चाहता हूं। निरीक्षण करें कि IEmployeeVisitorसार्वभौमिक रूप से इसकी वापसी प्रकार की IEmployeeमात्रा निर्धारित की गई है , जबकि इसे इसकी Acceptविधि के अंदर परिमाणित करता है - जो कि एक उच्च पद पर है। C # से हास्केल में क्लंकली का अनुवाद करना:

data IEmployeeVisitor r = IEmployeeVisitor {
    visitPermanent :: PermanentEmployee -> r,
    visitContractor :: Contractor -> r
}

newtype IEmployee = IEmployee {
    accept :: forall r. IEmployeeVisitor r -> r
}

इसलिए यह अब आपके पास है। जब आप जेनेरिक विधियों के प्रकार लिखते हैं तो उच्च-रैंक प्रकार C # में दिखाई देते हैं।


1
मैं यह जानना चाहूंगा कि क्या किसी और ने उच्च-रैंक प्रकारों के लिए C # / Java / Blub के समर्थन के बारे में लिखा है। यदि आप, प्रिय पाठक, ऐसे किसी भी संसाधन के बारे में जानते हैं तो कृपया उन्हें मेरा रास्ता भेजें!
बेंजामिन हॉजसन


-2

ऑब्जेक्ट ओरिएंटेड भाषाओं से परिचित लोगों के लिए, एक उच्च-रैंक फ़ंक्शन केवल एक सामान्य फ़ंक्शन है जो अपने तर्क के रूप में एक और सामान्य फ़ंक्शन की अपेक्षा करता है।

उदाहरण के लिए टाइपस्क्रिप्ट में आप लिख सकते हैं:

type WithId<T> = T & { id: number }
type Identifier = <T>(obj: T) => WithId<T>
type Identify = <TObj>(obj: TObj, f: Identifier) => WithId<TObj>

देखें कि जेनेरिक फ़ंक्शन प्रकार किस प्रकार के जेनेरिक फ़ंक्शन की Identifyमांग करता है Identifier? यह Identifyएक उच्च रैंक समारोह बनाता है।


यह sepp2k के उत्तर को क्या जोड़ता है?
दिफ्यार

या बेंजामिन हॉजसन का, उस मामले के लिए?
डेफ़र

1
मुझे लगता है कि आपने हॉजसन की बात को याद किया। Acceptएक रैंक -1 बहुरूपी प्रकार है, लेकिन यह एक विधि है IEmployee, जो स्वयं रैंक -2 है। अगर कोई मुझे देता है IEmployee, तो मैं इसे खोल सकता हूं और Acceptकिसी भी प्रकार से इसकी विधि का उपयोग कर सकता हूं ।
18

1
आपका उदाहरण भी रैंक -2 है, जिस Visiteeकक्षा से आप परिचय कराते हैं। एक फ़ंक्शन f :: Visitee e => T eहै (एक बार कक्षा के सामान को छोड़ दिया जाता है) अनिवार्य रूप से f :: (forall r. e -> Visitor e r -> r) -> T e। हास्केल 2010 आपको इस तरह की कक्षाओं का उपयोग करके सीमित रैंक -2 बहुरूपता से दूर होने देता है।
dfeuer

1
आप forallमेरे उदाहरण में तैर नहीं सकते । मेरे हाथ से कोई संदर्भ नहीं है, लेकिन आपको "अपने प्रकारों को स्क्रैप करें" में कुछ अच्छी तरह से मिल सकता है । उच्च-रैंक बहुरूपता वास्तव में टाइप-चेकिंग समस्याओं को पेश कर सकता है, लेकिन क्लास सिस्टम में निहित सीमित प्रकार ठीक है।
डेफ़र
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.