निर्भर टाइपिंग क्या है?


84

क्या कोई मुझ पर निर्भर टाइपिंग समझा सकता है? मेरे पास हास्केल, केयेन, एपिग्राम या अन्य कार्यात्मक भाषाओं में बहुत कम अनुभव है, इसलिए आप जिन शब्दों का उपयोग कर सकते हैं, वे जितना सरल हैं, मैं उसकी सराहना करूंगा!


तो क्या आप के बारे में समझ नहीं आया उदाहरण के लिए विकिपीडिया लेख?
कार्ल Knechtel

127
खैर, लेख लैम्ब्डा क्यूब्स के साथ खुलता है, जो मुझे कुछ प्रकार के भेड़ के मांस की तरह लगता है। तब यह λΠ2 सिस्टम पर चर्चा करने के लिए जाता है, और जैसा कि मैं विदेशी नहीं बोलता मैं उस अनुभाग को छोड़ देता हूं। फिर मैंने आगमनात्मक निर्माणों के कलन के बारे में पढ़ा, जो संयोगवश कलन, गर्मी हस्तांतरण, या निर्माण के साथ बहुत कम लगता है। एक भाषा तुलना तालिका देने के बाद, लेख समाप्त हो जाता है, और जब मैं पृष्ठ पर आता हूं तो मैं और अधिक भ्रमित हो जाता हूं।
निक

3
@ यह विकिपीडिया के साथ एक सामान्य समस्या है। मैंने कुछ साल पहले आपकी टिप्पणी देखी थी, और मैंने इसे तब से याद किया है। मैं इसे अब बुकमार्क कर रहा हूं।
डैनियल एच

जवाबों:


116

इस पर विचार करें: सभी सभ्य प्रोग्रामिंग भाषाओं में आप फ़ंक्शन लिख सकते हैं, जैसे

def f(arg) = result

यहां, fएक मूल्य लेता है argऔर एक मूल्य की गणना करता है result। यह मूल्यों से मूल्यों तक का कार्य है।

अब, कुछ भाषाएँ आपको बहुरूपता (उर्फ़ सामान्य) मान परिभाषित करने की अनुमति देती हैं:

def empty<T> = new List<T>()

यहां, emptyएक प्रकार लेता है Tऔर एक मूल्य की गणना करता है। यह एक प्रकार से मानों का कार्य है।

आमतौर पर, आप सामान्य प्रकार की परिभाषाएँ भी दे सकते हैं:

type Matrix<T> = List<List<T>>

यह परिभाषा एक प्रकार लेता है और यह एक प्रकार देता है। इसे एक प्रकार के प्रकार से एक फ़ंक्शन के रूप में देखा जा सकता है।

आम भाषाओं के लिए बहुत कुछ। एक भाषा को भरोसेमंद रूप से टाइप किया जाता है यदि यह मानों से प्रकारों के कार्यों को परिभाषित करते हुए, 4 वीं संभावना भी प्रदान करता है। या दूसरे शब्दों में, मान पर एक प्रकार की परिभाषा को पैरामीटर:

type BoundedInt(n) = {i:Int | i<=n}

कुछ मुख्यधारा की भाषाओं के कुछ नकली रूप हैं जिन्हें भ्रमित नहीं करना है। उदाहरण के लिए C ++ में, टेम्प्लेट पैरामीटर के रूप में मान ले सकते हैं, लेकिन उन्हें लागू होने पर संकलन-समय स्थिरांक होना चाहिए। वास्तव में निर्भर टाइप की भाषा में ऐसा नहीं है। उदाहरण के लिए, मैं ऊपर दिए गए प्रकार का उपयोग कर सकता हूं:

def min(i : Int, j : Int) : BoundedInt(j) =
  if i < j then i else j

यहां, फ़ंक्शन का परिणाम प्रकार वास्तविक तर्क मान पर निर्भर करता हैj , इस प्रकार शब्दावली।


नहीं है BoundedIntउदाहरण वास्तव में एक शोधन प्रकार, हालांकि? यह 'बहुत करीब' है, लेकिन वास्तव में 'निर्भर प्रकार' की तरह नहीं है, उदाहरण के लिए, इदरिस ने सबसे पहले dep.typing के बारे में एक ट्यूटोरियल में उल्लेख किया है।
नरफ़नर

3
@ नोइन, शोधन प्रकार वास्तव में निर्भर प्रकारों का एक सरल रूप है।
एंड्रियास रॉसबर्ग

22

आश्रित प्रकार, तर्क त्रुटियों के बड़े सेट को संकलन समय पर समाप्त करने में सक्षम बनाते हैं । इसे समझने के लिए फ़ंक्शन पर निम्नलिखित विनिर्देश पर विचार करें f:

फ़ंक्शन को इनपुट के रूप में fकेवल पूर्णांक भी लेना चाहिए ।

निर्भर प्रकारों के बिना आप ऐसा कुछ कर सकते हैं:

def f(n: Integer) := {
  if  n mod 2 != 0 then 
    throw RuntimeException
  else
    // do something with n
}

यहां कंपाइलर यह पता नहीं लगा सकता कि nक्या वास्तव में भी है, यानी कंपाइलर के दृष्टिकोण से निम्नलिखित अभिव्यक्ति ठीक है:

f(1)    // compiles OK despite being a logic error!

यह प्रोग्राम रन करेगा और फिर रनटाइम पर अपवाद को फेंक देगा, अर्थात, आपके प्रोग्राम में लॉजिक एरर है।

अब, निर्भर प्रकार आपको अधिक अभिव्यंजक बनाने में सक्षम बनाते हैं और आपको कुछ इस तरह लिखने में सक्षम करेंगे:

def f(n: {n: Integer | n mod 2 == 0}) := {
  // do something with n
}

यहाँ nनिर्भर प्रकार है {n: Integer | n mod 2 == 0}। यह इस रूप में जोर से पढ़ने में मदद कर सकता है

n पूर्णांकों के सेट का एक सदस्य ऐसा है कि प्रत्येक पूर्णांक 2 से विभाज्य है।

इस मामले में कंपाइलर एक लॉजिक एरर के कंपाइल टाइम में पता लगाएगा कि आपने कहां से एक विषम संख्या पास की है fऔर प्रोग्राम को पहली बार में निष्पादित करने से रोकेगा:

f(1)    // compiler error

यहाँ स्केल पथ-निर्भर प्रकारों का उपयोग करके एक उदाहरण दिया गया है कि हम fइस तरह की आवश्यकता को पूरा करते हुए फ़ंक्शन को लागू करने का प्रयास कैसे कर सकते हैं:

case class Integer(v: Int) {
  object IsEven { require(v % 2 == 0) }
  object IsOdd { require(v % 2 != 0) }
}

def f(n: Integer)(implicit proof: n.IsEven.type) =  { 
  // do something with n safe in the knowledge it is even
}

val `42` = Integer(42)
implicit val proof42IsEven = `42`.IsEven

val `1` = Integer(1)
implicit val proof1IsOdd = `1`.IsOdd

f(`42`) // OK
f(`1`)  // compile-time error

कैसे मूल्य कुंजी जानकारी में है nमूल्य के प्रकार में प्रकट होता है proofअर्थात् n.IsEven.type:

def f(n: Integer)(implicit proof: n.IsEven.type)
      ^                           ^
      |                           |
    value                       value

हम कहते हैं कि प्रकार मूल्यn.IsEven.type पर निर्भर करता है इसलिए शब्द निर्भर-प्रकार n


6
यह यादृच्छिक मूल्य से कैसे संबंधित है? उदाहरण के लिए, f(random())संकलन में त्रुटि होगी?
वोंग जिया हौ

6
fकुछ अभिव्यक्ति को लागू करने के लिए संकलक (आपकी मदद के बिना या बिना) की आवश्यकता होगी, ताकि अभिव्यक्ति हमेशा भी हो, और ऐसा कोई प्रमाण मौजूद नहीं है random()(क्योंकि यह वास्तव में विषम हो सकता है), इसलिए f(random())संकलन करने में विफल होगा।
मत्तीज

19

यदि आप C ++ को जानते हैं तो यह एक प्रेरक उदाहरण प्रदान करना आसान है:

मान लीजिए कि हमारे पास कुछ कंटेनर प्रकार और दो उदाहरण हैं

typedef std::map<int,int> IIMap;
IIMap foo;
IIMap bar;

और इस कोड के टुकड़े पर विचार करें (आप मान सकते हैं कि फू गैर-खाली है):

IIMap::iterator i = foo.begin();
bar.erase(i);

यह स्पष्ट कचरा है (और संभवतः डेटा संरचनाओं को दूषित करता है), लेकिन यह "इटूमीटर इन फू" और "इट्रेटर इन बार" के बाद से ठीक-ठीक टाइप करेगा। IIMap::iterator , भले ही वे पूरी तरह से असंगत हैं।

मुद्दा यह है कि एक इट्रेटर प्रकार केवल कंटेनर प्रकार पर निर्भर नहीं होना चाहिए, लेकिन वास्तव में कंटेनर ऑब्जेक्ट पर , अर्थात इसे "गैर-स्थिर सदस्य प्रकार" होना चाहिए:

foo.iterator i = foo.begin();
bar.erase(i);  // ERROR: bar.iterator argument expected

इस तरह की सुविधा, एक प्रकार (foo.iterator) को व्यक्त करने की क्षमता जो एक शब्द (फू) पर निर्भर करती है, वास्तव में निर्भरता का अर्थ है।

आप अक्सर इस सुविधा को नहीं देख पाने का कारण यह है कि यह कीड़े के एक बड़े डिब्बे को खोलता है: आप अचानक उन स्थितियों में समाप्त हो जाते हैं जहां, संकलन-समय पर जांच करने के लिए कि क्या दो प्रकार समान हैं, आप दो अभिव्यक्तियों को साबित करने के लिए समाप्त होते हैं समतुल्य हैं (हमेशा रनटाइम पर समान मान प्राप्त करेंगे)। परिणामस्वरूप, यदि आप विकिपीडिया की सूची की तुलना भरोसेमंद रूप से टाइप की गई भाषाओं की अपनी प्रमेय की सूची से करते हैं, तो यह साबित करता है कि आपको संदिग्ध मानसिकता दिखाई दे सकती है। ;-)


4

पुस्तक के प्रकार और प्रोग्रामिंग भाषाएँ उद्धृत करना (30.5):

इस पुस्तक का अधिकांश विभिन्न प्रकार के अमूर्त तंत्र को औपचारिक बनाने से संबंधित है। बस टाइप किए गए लैम्ब्डा-कैलकुलस में, हमने एक शब्द लेने और एक सबटेरम को अमूर्त करने के संचालन को औपचारिक रूप दिया, एक फ़ंक्शन को उत्पन्न किया जो बाद में इसे अलग-अलग शब्दों में लागू करके त्वरित किया जा सकता है। सिस्टम में , हमने टाइप किए गए लैम्ब्डा-कैलकुलस "एक स्तर ऊपर," के प्रकारों को फिर से टाइप किया और एक टाइप ऑपरेटर को प्राप्त करने के लिए एक उपप्रकार को बाहर निकाला और बाद में इसे विभिन्न प्रकारों में लागू करके त्वरित किया जा सकता है। अमूर्त के इन सभी रूपों के सोचने का एक सुविधाजनक तरीका अभिव्यक्ति के परिवारों के संदर्भ में है, जो अन्य अभिव्यक्तियों द्वारा अनुक्रमित है। एक साधारण लंबोदर अमूर्त शब्दों का एक परिवार है जिसे शब्दों द्वारा अनुक्रमित किया जाता है । इसी प्रकार, एक प्रकार का अमूर्त F , हमने एक शब्द लेने और एक प्रकार से अमूर्त करने के संचालन पर विचार किया, एक शब्द की उपज जो इसे विभिन्न प्रकारों पर लागू करके त्वरित किया जा सकता है। मेंλωλx:T1.t2[x -> s]t1sλX::K1.t2 प्रकारों द्वारा अनुक्रमित शर्तों का एक परिवार है, और एक प्रकार का ऑपरेटर प्रकारों द्वारा अनुक्रमित प्रकारों का एक परिवार है।

  • λx:T1.t2 शर्तों द्वारा अनुक्रमित परिवार

  • λX::K1.t2 प्रकारों द्वारा अनुक्रमित शब्दों का परिवार

  • λX::K1.T2 प्रकारों द्वारा अनुक्रमित प्रकार के परिवार

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

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.