मैं वास्तव में हास्केल में कुशल नहीं हूं, इसलिए यह एक बहुत आसान सवाल हो सकता है।
रैंक 2Types किस भाषा की सीमा को हल करते हैं? हास्केल में कार्य पहले से ही बहुरूपी तर्कों का समर्थन नहीं करते हैं?
मैं वास्तव में हास्केल में कुशल नहीं हूं, इसलिए यह एक बहुत आसान सवाल हो सकता है।
रैंक 2Types किस भाषा की सीमा को हल करते हैं? हास्केल में कार्य पहले से ही बहुरूपी तर्कों का समर्थन नहीं करते हैं?
जवाबों:
हास्केल में कार्य पहले से ही बहुरूपी तर्कों का समर्थन नहीं करते हैं?
वे करते हैं, लेकिन केवल रैंक 1. इसका मतलब यह है कि जब आप एक फ़ंक्शन लिख सकते हैं जो इस एक्सटेंशन के बिना विभिन्न प्रकार के तर्क लेता है, तो आप एक फ़ंक्शन नहीं लिख सकते हैं जो एक ही आह्वान में विभिन्न प्रकार के तर्क का उपयोग करता है।
उदाहरण के लिए निम्नलिखित फ़ंक्शन को इस एक्सटेंशन के बिना टाइप नहीं किया जा सकता है क्योंकि g
इसका परिभाषा में विभिन्न तर्क प्रकारों के साथ उपयोग किया जाता है f
:
f g = g 1 + g "lala"
ध्यान दें कि किसी अन्य फ़ंक्शन के तर्क के रूप में एक पॉलीमॉर्फिक फ़ंक्शन को पास करना पूरी तरह से संभव है। तो ऐसा कुछ map id ["a","b","c"]
पूरी तरह से कानूनी है। लेकिन फ़ंक्शन केवल इसे मोनोमोर्फिक के रूप में उपयोग कर सकता है। उदाहरण में map
उपयोग करता है id
जैसे कि यह टाइप किया गया था String -> String
। और निश्चित रूप से आप इसके बजाय दिए गए प्रकार का एक सरल मोनोमोर्फिक फ़ंक्शन भी पास कर सकते हैं id
। बिना रेंक 2 टार्ट के किसी फ़ंक्शन के लिए यह आवश्यक नहीं है कि उसके तर्क में एक पॉलीमॉर्फिक फ़ंक्शन होना चाहिए और इस तरह इसे पॉलीमॉर्फिक फ़ंक्शन के रूप में उपयोग करने का कोई तरीका भी नहीं है।
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
किया जा सकता है - क्या होगा ?
जब तक आप सिस्टम एफ का सीधे अध्ययन नहीं करते हैं, तब तक उच्च-श्रेणी के बहुरूपता को समझना मुश्किल है , क्योंकि हास्केल को सादगी के हित में आप से विवरण छिपाने के लिए डिज़ाइन किया गया है।
लेकिन मूल रूप से, मोटे तौर पर विचार यह है कि बहुरूपी प्रकारों के पास वास्तव में वह a -> b
रूप नहीं होता है जैसा वे हास्केल में करते हैं; वास्तव में, वे इस तरह दिखते हैं, हमेशा स्पष्ट मात्रात्मक के साथ:
id :: ∀a.a → a
id = Λt.λx:t.x
यदि आपको "∀" प्रतीक नहीं पता है, तो इसे "सभी के लिए" के रूप में पढ़ा जाता है; ∀x.dog(x)
का अर्थ है "सभी एक्स के लिए, एक्स एक कुत्ता है।" "" "प्रकार के मापदंडों पर अमूर्त करने के लिए उपयोग किया जाने वाला कैपिटल लैम्ब्डा है; दूसरी पंक्ति क्या कहती है कि आईडी एक प्रकार्य है जो एक प्रकार लेता है t
, और फिर एक फ़ंक्शन देता है जो उस प्रकार से पैराट्राइज्ड होता है।
आप देखते हैं, सिस्टम एफ में, आप अभी उस तरह से एक फ़ंक्शन लागू नहीं कर सकते हैं id
; पहले आपको λ-function को टाइप करने के लिए need-function को एक प्रकार से लागू करने की आवश्यकता होती है, जिसे आप किसी मान पर लागू करते हैं। उदाहरण के लिए:
(Λt.λx: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
, मेरा मानना है कि इसका कारण यह है कि यह इस तरह की तकनीक का उपयोग पर्दे के पीछे कर रहा है।
exists
कीवर्ड था , तो आप एक अस्तित्वगत प्रकार (उदाहरण के लिए) को परिभाषित कर सकते हैं data Any = Any (exists a. a)
, जहां Any :: (exists a. a) -> Any
। ≡xP (x) → Q ∀ (PxP (x)) → Q का उपयोग करके, हम यह निष्कर्ष निकाल सकते हैं कि Any
एक प्रकार भी हो सकता है forall a. a -> Any
और यही वह जगह है जहाँ से forall
कीवर्ड आता है। मेरा मानना है कि जीएचसी द्वारा कार्यान्वित अस्तित्वगत प्रकार केवल सामान्य डेटा प्रकार हैं जो सभी आवश्यक टाइपकास्ट शब्दकोशों को भी ले जाते हैं (मैं इसे वापस करने के लिए संदर्भ नहीं पा सका, क्षमा करें)।
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
।
लुइस कैसिलस का जवाब बहुत अच्छी जानकारी देता है कि रैंक 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
मोनाड को सुरक्षित बनाने के लिए महत्वपूर्ण है ।
उच्च-श्रेणी के प्रकार उतने विदेशी नहीं हैं जितने कि अन्य उत्तर दिए गए हैं। मानो या न मानो, कई ऑब्जेक्ट-ओरिएंटेड भाषाएँ (जावा और 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 # में दिखाई देते हैं।
स्टैनफोर्ड में ब्रायन ओ'सुलीवन के हास्केल कोर्स से स्लाइड ने मुझे समझने में मदद की Rank2Types
।
ऑब्जेक्ट ओरिएंटेड भाषाओं से परिचित लोगों के लिए, एक उच्च-रैंक फ़ंक्शन केवल एक सामान्य फ़ंक्शन है जो अपने तर्क के रूप में एक और सामान्य फ़ंक्शन की अपेक्षा करता है।
उदाहरण के लिए टाइपस्क्रिप्ट में आप लिख सकते हैं:
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
एक उच्च रैंक समारोह बनाता है।
Accept
एक रैंक -1 बहुरूपी प्रकार है, लेकिन यह एक विधि है IEmployee
, जो स्वयं रैंक -2 है। अगर कोई मुझे देता है IEmployee
, तो मैं इसे खोल सकता हूं और Accept
किसी भी प्रकार से इसकी विधि का उपयोग कर सकता हूं ।
Visitee
कक्षा से आप परिचय कराते हैं। एक फ़ंक्शन f :: Visitee e => T e
है (एक बार कक्षा के सामान को छोड़ दिया जाता है) अनिवार्य रूप से f :: (forall r. e -> Visitor e r -> r) -> T e
। हास्केल 2010 आपको इस तरह की कक्षाओं का उपयोग करके सीमित रैंक -2 बहुरूपता से दूर होने देता है।
forall
मेरे उदाहरण में तैर नहीं सकते । मेरे हाथ से कोई संदर्भ नहीं है, लेकिन आपको "अपने प्रकारों को स्क्रैप करें" में कुछ अच्छी तरह से मिल सकता है । उच्च-रैंक बहुरूपता वास्तव में टाइप-चेकिंग समस्याओं को पेश कर सकता है, लेकिन क्लास सिस्टम में निहित सीमित प्रकार ठीक है।