केवल Hindley-Milner प्रकार प्रणाली का उपयोग करके एक सूची निर्धारित करें


10

मैं एक छोटे से लैम्ब्डा कैलकुलस कंपाइलर पर काम कर रहा हूं, जिसमें एक काम करने वाला हिंडले-मिलनर टाइप इंट्रेंस सिस्टम है और अब यह रिकर्सिव लेट्स (लिंक्ड कोड में नहीं) का भी समर्थन करता है, जो मुझे समझ में आता है कि इसे पूरा करने के लिए पर्याप्त होना चाहिए

अब समस्या यह है कि मुझे यह पता नहीं है कि इसे सूचियों का समर्थन कैसे करना है, या क्या यह पहले से ही उनका समर्थन करता है और मुझे उन्हें सांकेतिक शब्दों में बदलना चाहिए। मैं नए नियमों को टाइप सिस्टम में शामिल किए बिना उन्हें परिभाषित करने में सक्षम होना चाहता हूं।

सबसे आसान तरीका जो मैं सोच सकता हूँ कि एक सूची xकुछ ऐसी है जो या तो null(या खाली सूची) है, या एक जोड़ी जिसमें एक xऔर दोनों की सूची है x। लेकिन ऐसा करने के लिए मुझे जोड़े और / या परिभाषित करने में सक्षम होना चाहिए, जो मेरा मानना ​​है कि उत्पाद और योग प्रकार हैं।

लगता है कि मैं इस तरह से जोड़े परिभाषित कर सकता हूं:

pair = λabf.fab
first = λp.p(λab.a)
second = λp.p(λab.b)

चूंकि pairप्रकार होता है a -> (b -> ((a -> (b -> x)) -> x)), पास करने के बाद, कहते हैं, एक intऔर एक stringहै, यह प्रकार के साथ कुछ उपज होता (int -> (string -> x)) -> xहै, जो की एक जोड़ी का प्रतिनिधित्व किया जाएगा intऔर string। मुझे यहाँ क्या परेशान करता है कि अगर यह एक जोड़ी का प्रतिनिधित्व करता है, तो यह तार्किक रूप से समान क्यों नहीं है और न ही प्रस्ताव का तात्पर्य है int and string। हालांकि, इसके बराबर है (((int and string) -> x) -> x), जैसे कि मैं केवल उत्पाद प्रकार के मापदंडों के रूप में कार्य कर सकता हूं। यह उत्तरइस समस्या को हल करने के लिए लगता है, लेकिन मुझे नहीं पता कि प्रतीकों का वह क्या उपयोग करता है। इसके अलावा, अगर यह वास्तव में एक उत्पाद प्रकार को एनकोड नहीं करता है, तो क्या मैं कुछ उत्पाद प्रकारों के साथ कर सकता हूं जो मैं ऊपर जोड़े की मेरी परिभाषा के साथ नहीं कर सकता था (मैं भी उसी तरह n-tuples को परिभाषित कर सकता हूं)? यदि नहीं, तो क्या यह इस तथ्य का खंडन नहीं करेगा कि आप (एएफएआईके) संयोजन को केवल निहितार्थ का उपयोग करके व्यक्त नहीं कर सकते हैं?

इसके अलावा, योग प्रकार के बारे में कैसे? क्या मैं किसी प्रकार केवल फ़ंक्शन प्रकार का उपयोग करके इसे एनकोड कर सकता हूं? यदि हां, तो क्या यह सूचियों को परिभाषित करने के लिए पर्याप्त होगा? या फिर, क्या मेरे प्रकार प्रणाली का विस्तार किए बिना सूचियों को परिभाषित करने का कोई अन्य तरीका है? और यदि नहीं, तो मुझे क्या बदलाव करने की आवश्यकता होगी यदि मैं इसे यथासंभव सरल रखना चाहता हूं?

कृपया ध्यान रखें कि मैं एक कंप्यूटर प्रोग्रामर हूं, लेकिन कंप्यूटर साइंटिस्ट नहीं हूं और न ही गणितज्ञ और गणित पढ़ने में बहुत खराब हूं।

संपादित करें: मुझे यकीन नहीं है कि मैंने अब तक जो भी लागू किया है उसका तकनीकी नाम क्या है, लेकिन मेरे पास मूल रूप से कोड है जो मैंने ऊपर लिंक किया है, जो एक बाधा पीढ़ी एल्गोरिथ्म है जो अनुप्रयोगों, अमूर्त और चर के लिए नियमों का उपयोग करता है। हिंले-मिलनर एल्गोरिथ्म और फिर एक एकीकरण एल्गोरिथ्म से जो प्रमुख प्रकार प्राप्त करता है। उदाहरण के लिए, अभिव्यक्ति \a.aप्रकार का उत्पादन करेगी a -> a, और अभिव्यक्ति में \a.(a a)चेक त्रुटि होगी। इसके शीर्ष पर, वास्तव में एक letनियम नहीं है, लेकिन एक ऐसा फ़ंक्शन है जिसका एक ही प्रभाव है जो आपको इस छद्म कोड जैसे पुनरावर्ती वैश्विक कार्यों को परिभाषित करने देता है:

GetTypeOfGlobalFunction(term, globalScope, nameOfFunction)
{
    // Here 'globalScope' contains a list of name-value pair where every value is of class 'ClosedType', 
    // meaning their type will be cloned before unified in the unification algorithm so that they can be used polymorphically 
    tempType = new TypeVariable() // Assign a dummy type to `tempType`, say, type 'x'.
    // The next line creates an scope with everything in 'globalScope' plus the 'nameOfFunction = tempType' name-value pair
    tempScope = new Scope(globalScope, nameOfFunction, tempType) 
    type = TypeOfTerm(term, tempScope) // Calculate the type of the term 
    Unify(tempType, type)
    return type
    // After returning, the code outside will create a 'ClosedType' using the returned type and add it to the global scope.
}

कोड मूल रूप से हमेशा की तरह शब्द का प्रकार प्राप्त करता है, लेकिन एकीकरण करने से पहले, यह फ़ंक्शन का नाम एक डमी प्रकार के साथ परिभाषित किया जा रहा है ताकि इसे स्कोप के रूप में इस्तेमाल किया जा सके।

संपादित करें 2: मुझे बस एहसास हुआ कि मुझे पुनरावर्ती प्रकारों की भी आवश्यकता होगी, जो मेरे पास नहीं है, जैसे मुझे चाहिए कि एक सूची को परिभाषित करने के लिए।


क्या आप थोड़ा और विशिष्ट हो सकते हैं जो आपने वास्तव में लागू किया है? क्या आपने केवल टाइप किए गए लैम्ब्डा कैलकुलस (पुनरावर्ती परिभाषाओं के साथ) को लागू किया है और इसे पैरामीट्रिक बहुरूपता हिंडले-मिलनर शैली दिया है? या क्या आपने दूसरे क्रम के बहुरूपी लैंबडा कैलकुलस को लागू किया है?
बाउर

शायद मैं एक आसान तरीके से पूछ सकता हूं: अगर मैं ओकेएमएल या एसएमएल लेता हूं और इसे शुद्ध लंबोदर शर्तों और पुनरावर्ती परिभाषाओं तक सीमित करता हूं, तो क्या आप बात कर रहे हैं?
बॉयर

@AndrejBauer: मैंने प्रश्न संपादित किया है। मुझे OCaml और SML के बारे में निश्चित नहीं है, लेकिन मुझे पूरा यकीन है कि अगर आप हास्केल लेते हैं और इसे लैम्ब्डा शब्दों तक सीमित रखते हैं और शीर्ष स्तर की पुनरावर्ती अनुमति देता है (जैसे कि let func = \x -> (func x)) आपको वही मिलता है जो मेरे पास है।
जुआन

1
अपने प्रश्न को बेहतर बनाने के लिए, इस मेटा पोस्ट को देखें
जुहो

जवाबों:


13

जोड़े

यह एन्कोडिंग जोड़े का चर्च एन्कोडिंग है। इसी तरह की तकनीक बुलियन, पूर्णांक, सूची और अन्य डेटा संरचनाओं को एन्कोड कर सकती है।

x:a; y:bpair x y(a -> b -> t) -> t¬

(टी)टी¬(¬¬टी)टी(¬टी)टी()टी
ab tpairt

pairजोड़ी प्रकार के लिए एक निर्माता है firstऔर secondविनाशकारी हैं। (ये ऑब्जेक्ट-ओरिएंटेड प्रोग्रामिंग में उपयोग किए जाने वाले समान शब्द हैं; यहां शब्दों का एक अर्थ है जो उन प्रकारों और शर्तों की तार्किक व्याख्या से संबंधित है जो मैं यहां नहीं जाऊंगा।) सहज रूप से, विनाशकों ने आपको एक्सेस करने दिया। ऑब्जेक्ट में और कंस्ट्रक्टर्स एक फ़ंक्शन के रूप में विध्वंसक के लिए मार्ग प्रशस्त करते हैं जो कि वे ऑब्जेक्ट के हिस्सों पर लागू होते हैं। इस सिद्धांत को अन्य प्रकारों पर लागू किया जा सकता है।

रकम

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

let case w = λf. λg. w f g           case : ((a->t) -> (b->t) -> t) -> (a->t) -> (b->t) -> t
  (* or simply let case w = w *)
let left x = λf. λg. f x             left : a -> ((a->t) -> (b->t) -> t)
let right y = λf. λg. g x            right : b -> ((a->t) -> (b->t) -> t)

मुझे प्रकार संक्षिप्त करते हैं (a->t) -> (b->t) -> tके रूप में SUM(a,b)(t)। फिर विध्वंसक और निर्माणकर्ता के प्रकार हैं:

case : SUM(a,b)(t) -> (a->t) -> (b->t) -> t
left : a -> SUM(a,b)(t)
right : b -> SUM(a,b)(t)

इस प्रकार

case (left x) f g → f x
case (rightt y) f g → g y

सूचियाँ

सूची के लिए, फिर से उसी सिद्धांत को लागू करें। एक सूची जिसके तत्वों का प्रकार aदो तरह से बनाया जा सकता है: यह एक खाली सूची हो सकती है, या यह एक तत्व (प्रमुख) हो सकती है और एक सूची (पूंछ) हो सकती है। जोड़े के साथ तुलना में, जब विध्वंसक की बात आती है तो थोड़ा मोड़ आता है: आपके पास दो अलग-अलग विध्वंसक नहीं हो सकते हैं headऔर tailक्योंकि वे केवल गैर-रिक्त सूचियों पर काम करेंगे। आपको दो तर्कों के साथ एक एकल विध्वंसक की आवश्यकता है, जिनमें से एक शून्य मामले के लिए एक 0-तर्क फ़ंक्शन (यानी एक मान) है, और दूसरा विपक्ष मामले के लिए 2-तर्क फ़ंक्शन है। जैसे कार्य is_empty, headऔर tailउससे प्राप्त किया जा सकता है। जैसे रकम के मामले में, सूची सीधे अपने स्वयं के विध्वंसक कार्य की है।

let nil = λn. λc. n
let cons h t = λn. λc. c h t
let is_empty l = l true (λh. λt. false) 
let head l default = l default (λh. λt. h)
let tail l default = l default (λh. λt. t)

consconsconsटीटी1,...,टीn

जैसा कि आप अनुमान लगाते हैं, यदि आप एक प्रकार को परिभाषित करना चाहते हैं जिसमें केवल सजातीय सूची शामिल है, तो आपको पुनरावर्ती प्रकार की आवश्यकता है। क्यों? आइए सूची के प्रकार को देखें। एक सूची को एक फ़ंक्शन के रूप में एन्कोड किया गया है जो दो तर्क लेता है: खाली सूचियों पर वापस जाने के लिए मान, और एक कॉन्स सेल पर लौटने के लिए मान की गणना करने के लिए फ़ंक्शन। आज्ञा देना aतत्व प्रकार हो, bसूची के प्रकार, और cप्रकार नाशक द्वारा दिया हो। सूची का प्रकार है

a -> (a -> b -> c) -> c

सूची को सजातीय बनाते हुए कहा जा रहा है कि यदि यह एक वाणिज्य प्रकोष्ठ है, तो पूँछ को पूरे के समान ही होना चाहिए, अर्थात यह बाधा जोड़ता है

a -> (a -> b -> c) -> c = b

हिंडले-मिलनर प्रकार प्रणाली को इस तरह के पुनरावर्ती प्रकारों के साथ बढ़ाया जा सकता है, और वास्तव में व्यावहारिक प्रोग्रामिंग भाषाएं ऐसा करती हैं। व्यावहारिक प्रोग्रामिंग भाषाओं में ऐसे "नग्न" समीकरणों को अस्वीकार करना और डेटा निर्माता की आवश्यकता होती है, लेकिन यह अंतर्निहित सिद्धांत की आंतरिक आवश्यकता नहीं है। डेटा कन्स्ट्रक्टर की आवश्यकता को टाइप इंफ़ेक्शन को सरल बनाता है, और व्यवहार में उन कार्यों को स्वीकार करने से बचता है जो वास्तव में छोटी गाड़ी हैं, लेकिन कुछ अनजाने में बाधा के साथ टाइप करने योग्य होते हैं जो एक कठिन-टू-समझने प्रकार त्रुटि का कारण बनता है जहां फ़ंक्शन का उपयोग किया जाता है। यही कारण है कि, उदाहरण के लिए, OCaml केवल गैर-डिफ़ॉल्ट -rectypesसंकलक विकल्प के साथ बिना सोचे-समझे पुनरावर्ती प्रकार स्वीकार करता है। यहाँ OCaml वाक्य रचना में ऊपर परिभाषाएँ हैं, साथ में सजातीय सूचियों के लिए एक प्रकार की परिभाषा के लिए संकेतन का उपयोग करते हुएअन्य प्रकार के पुनरावर्ती प्रकार : type_expression as 'aइसका मतलब है कि प्रकार type_expressionचर के साथ एकीकृत है 'a

# let nil = fun n c -> n;;
val nil : 'a -> 'b -> 'a = <fun>
# let cons h t = fun n c -> c h t;;
val cons : 'a -> 'b -> 'c -> ('a -> 'b -> 'd) -> 'd = <fun>
# let is_empty l = l true (fun h t -> false);;
val is_empty : (bool -> ('a -> 'b -> bool) -> 'c) -> 'c = <fun>
# let head l default = l default (fun h t -> h);;
val head : ('a -> ('b -> 'c -> 'b) -> 'd) -> 'a -> 'd = <fun>
# let tail l default = l default (fun h t -> t);;
val tail : ('a -> ('b -> 'c -> 'c) -> 'd) -> 'a -> 'd = <fun>
# type ('a, 'b, 'c) ulist = 'c -> ('a -> 'b -> 'c) -> 'c;;
type ('a, 'b, 'c) ulist = 'c -> ('a -> 'b -> 'c) -> 'c
# is_empty (cons 1 nil);;
- : bool = false
# head (cons 1 nil) 0;;
- : int = 1
# head (tail (cons 1 (cons 2.0 nil)) nil) 0.;;
- : float = 2.

(* -rectypes is required for what follows *)
# type ('a, 'b, 'c) rlist = 'c -> ('a -> 'b -> 'c) -> 'c as 'b;;
type ('a, 'b, 'c) rlist = 'b constraint 'b = 'c -> ('a -> 'b -> 'c) -> 'c
# let rcons = (cons : 'a -> ('a, 'b, 'c) rlist -> ('a, 'b, 'c) rlist);;
val rcons :
  'a ->
  ('a, 'c -> ('a -> 'b -> 'c) -> 'c as 'b, 'c) rlist -> ('a, 'b, 'c) rlist =
  <fun>
# head (rcons 1 (rcons 2 nil)) 0;;
- : int = 1
# tail (rcons 1 (rcons 2 nil)) nil;;
- : 'a -> (int -> 'a -> 'a) -> 'a as 'a = <fun>
# rcons 1 (rcons 2.0 nil);;
Error: This expression has type
         (float, 'b -> (float -> 'a -> 'b) -> 'b as 'a, 'b) rlist = 'a
       but an expression was expected of type
         (int, 'b -> (int -> 'c -> 'b) -> 'b as 'c, 'b) rlist = 'c

सिलवटों

इसे थोड़ा और आम तौर पर देखते हुए, डेटा संरचना का प्रतिनिधित्व करने वाला कार्य क्या है?

  • nn
  • (एक्स,y)एक्सy
  • मैंnमैं(एक्स)मैंएक्स
  • [एक्स1,...,एक्सn]

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


आप पूर्णांक, जोड़े, रकम के " चर्च एन्कोडिंग" का उल्लेख करते हैं , लेकिन सूचियों के लिए आप स्कॉट एन्कोडिंग देते हैं। मुझे लगता है कि यह उन लोगों के लिए थोड़ा भ्रमित हो सकता है जो आगमनात्मक प्रकारों के एन्कोडिंग से परिचित नहीं हैं।
स्टीफन गिमेनेज

इसलिए मूल रूप से, मेरी जोड़ी का प्रकार वास्तव में एक उत्पाद प्रकार नहीं है क्योंकि इस प्रकार का एक फ़ंक्शन केवल वापस आ सकता है tऔर उस तर्क को अनदेखा कर सकता है जो लेने वाला है aऔर b(जो वास्तव (a and b) or tमें कह रहा है)। और लगता है कि मुझे रकम के साथ एक ही तरह की परेशानी होगी। और भी, पुनरावर्ती प्रकारों के बिना मेरे पास सजातीय सूची नहीं होगी। तो कुछ शब्दों में, क्या आप कह रहे हैं कि मुझे सजातीय सूची प्राप्त करने के लिए योग, उत्पाद और पुनरावर्ती प्रकार के नियम जोड़ने चाहिए?
जुआन

क्या आपका मतलब case (right y) f g → g yआपके Sums सेक्शन के अंत में था ?
जुआन

@ StéphaneGimenez मुझे एहसास नहीं हुआ था। मुझे इन सांकेतिक शब्दों में टाइप की दुनिया में काम करने की आदत नहीं है। क्या आप स्कॉट एन्कोडिंग बनाम चर्च एन्कोडिंग के लिए एक संदर्भ दे सकते हैं?
गिल्स एसओ- बुराई को रोकें '

@JuanLuisSoldi आपने शायद सुना है कि "कोई समस्या नहीं है जो कि अतिरिक्त स्तर पर अप्रत्यक्ष रूप से हल नहीं हो सकती है"। चर्च एन्कोडिंग फ़ंक्शन कॉल के स्तर को जोड़कर डेटा संरचनाओं को फ़ंक्शन के रूप में एन्कोड करता है: डेटा संरचना एक दूसरे क्रम का फ़ंक्शन बन जाता है जिसे आप फ़ंक्शन पर लागू करने के लिए भागों पर कार्य करते हैं। यदि आप एक सजातीय सूची प्रकार चाहते हैं, तो आपको इस तथ्य से निपटना होगा कि पूंछ का प्रकार पूरी सूची के प्रकार के समान है। मुझे लगता है कि इसमें एक प्रकार की पुनरावृत्ति शामिल है।
गाइल्स का SO- बुराई से रोकना '

2

आप टैग और मूल्यों के साथ उत्पाद प्रकारों के रूप में योग प्रकार का प्रतिनिधित्व कर सकते हैं। इस मामले में, हम एक बिट को धोखा दे सकते हैं और एक टैग का उपयोग अशक्त या नहीं का प्रतिनिधित्व करने के लिए कर सकते हैं, दूसरा टैग सिर / पूंछ जोड़ी का प्रतिनिधित्व करता है।

हम सामान्य तरीके से बुलियन को परिभाषित करते हैं:

true = λi.λe.i
false = λi.λe.e
if = λcond.λthen.λelse.(cond then else)

एक सूची तब एक तत्व है जिसमें पहला तत्व बूलियन के रूप में है, और दूसरा तत्व सिर / पूंछ जोड़ी के रूप में है। कुछ मूल सूची कार्य:

isNull = λl.(first l)
null = pair false false     --The second element doesn't matter in this case
cons = λh.λt.(pair true (pair h t ))
head = λl.(fst (snd l))   --This is a partial function
tail = λl.(snd (snd l))   --This is a partial function  

map = λf.λl.(if (isNull l)
                 null 
                 (cons (f (head l)) (map f (tail l) ) ) 

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