अवलोकन
टाइप-स्तरीय प्रोग्रामिंग में पारंपरिक, मूल्य-स्तरीय प्रोग्रामिंग के साथ कई समानताएं हैं। हालांकि, मूल्य-स्तरीय प्रोग्रामिंग के विपरीत, जहां गणना रनटाइम में होती है, टाइप-स्तरीय प्रोग्रामिंग में, गणना समय पर होती है। मैं मान-स्तर पर प्रोग्रामिंग और टाइप-स्तर पर प्रोग्रामिंग के बीच समानताएं खींचने की कोशिश करूंगा।
उदाहरण
टाइप-स्तरीय प्रोग्रामिंग में दो मुख्य प्रतिमान हैं: "ऑब्जेक्ट-ओरिएंटेड" और "फंक्शनल"। यहाँ से जुड़े अधिकांश उदाहरण ऑब्जेक्ट-ओरिएंटेड प्रतिमान का अनुसरण करते हैं।
ऑब्जेक्ट-ओरिएंटेड प्रतिमान में टाइप-स्तरीय प्रोग्रामिंग का एक अच्छा, काफी सरल उदाहरण है , जिसे लैम्ब्डा कैलकुलस के कार्यान्वयन में पाया जा सकता है, जिसे यहां दोहराया गया है:
// Abstract trait
trait Lambda {
type subst[U <: Lambda] <: Lambda
type apply[U <: Lambda] <: Lambda
type eval <: Lambda
}
// Implementations
trait App[S <: Lambda, T <: Lambda] extends Lambda {
type subst[U <: Lambda] = App[S#subst[U], T#subst[U]]
type apply[U] = Nothing
type eval = S#eval#apply[T]
}
trait Lam[T <: Lambda] extends Lambda {
type subst[U <: Lambda] = Lam[T]
type apply[U <: Lambda] = T#subst[U]#eval
type eval = Lam[T]
}
trait X extends Lambda {
type subst[U <: Lambda] = U
type apply[U] = Lambda
type eval = X
}
जैसा कि उदाहरण में देखा जा सकता है, टाइप-स्तरीय प्रोग्रामिंग आय के लिए वस्तु-उन्मुख प्रतिमान निम्नानुसार है:
- पहला: विभिन्न अमूर्त प्रकार के फ़ील्ड के साथ एक अमूर्त विशेषता को परिभाषित करें (एक सार फ़ील्ड के लिए नीचे देखें)। यह गारंटी देने के लिए एक टेम्प्लेट है कि कुछ प्रकार के फ़ील्ड बिना कार्यान्वयन के सभी कार्यान्वयन में मौजूद हैं। लैम्ब्डा पथरी उदाहरण में, को यह मेल खाती है
trait Lambda
कि गारंटी देता है कि निम्न प्रकार के होते हैं: subst
, apply
, औरeval
।
- अगला: उन उपग्रहों को परिभाषित करें जो अमूर्त विशेषता का विस्तार करते हैं और विभिन्न अमूर्त प्रकार के क्षेत्रों को लागू करते हैं
- अक्सर, इन उपग्रहों को तर्कों के साथ मानकीकृत किया जाएगा। लैम्ब्डा कैलकुलस उदाहरण में, उपप्रकार वे होते हैं
trait App extends Lambda
जो दो प्रकारों से ( S
और T
, दोनों के उप-प्रकार होने चाहिए Lambda
), trait Lam extends Lambda
एक प्रकार से मानकीकृत ( T
), औरtrait X extends Lambda
(जो कि पैरामीटर नहीं है)।
- उपक्षेत्र के प्रकार के मापदंडों का हवाला देकर और कभी-कभी हैश ऑपरेटर के माध्यम से अपने प्रकार के क्षेत्रों को संदर्भित करके टाइप फ़ील्ड लागू
#
किया जाता है : (जो डॉट ऑपरेटर के समान है: .
मूल्यों के लिए)। App
लैम्ब्डा कैलकुलस उदाहरण के लक्षण में, प्रकार eval
निम्नानुसार लागू किया गया है type eval = S#eval#apply[T]
:। यह अनिवार्य रूप eval
से विशेषता के पैरामीटर के प्रकार को S
बुला रहा है, और परिणाम पर apply
पैरामीटर के साथ कॉल कर रहा है T
। ध्यान दें, S
एक eval
प्रकार की गारंटी दी जाती है क्योंकि पैरामीटर इसे उप-प्रकार निर्दिष्ट करता है Lambda
। इसी तरह, परिणाम का eval
एक apply
प्रकार होना चाहिए , क्योंकि यह एक उपप्रकार के Lambda
रूप में निर्दिष्ट है, जैसा कि अमूर्त विशेषता में निर्दिष्ट है Lambda
।
फ़ंक्शनल प्रतिमान में बहुत सारे मानकीकृत प्रकार के कंस्ट्रक्टर को परिभाषित करना शामिल होता है, जो लक्षण में एक साथ समूहीकृत नहीं होते हैं।
मूल्य-स्तरीय प्रोग्रामिंग और टाइप-स्तरीय प्रोग्रामिंग के बीच तुलना
- अमूर्त वर्ग
- मूल्य-स्तर:
abstract class C { val x }
- टाइप-स्तर:
trait C { type X }
- पथ निर्भर प्रकार
C.x
(वस्तु सी में फ़ंक्शन फ़ील्ड मान / फ़ंक्शन x)
C#x
(विशेषता सी में फ़ील्ड प्रकार x संदर्भित करना)
- फ़ंक्शन हस्ताक्षर (कोई कार्यान्वयन नहीं)
- मूल्य-स्तर:
def f(x:X) : Y
- प्रकार-स्तर:
type f[x <: X] <: Y
(इसे "टाइप कंस्ट्रक्टर" कहा जाता है और आमतौर पर अमूर्त विशेषता में होता है)
- समारोह कार्यान्वयन
- मूल्य-स्तर:
def f(x:X) : Y = x
- टाइप-स्तर:
type f[x <: X] = x
- सशर्त,
- समानता की जाँच करना
- मूल्य-स्तर:
a:A == b:B
- टाइप-स्तर:
implicitly[A =:= B]
- मूल्य-स्तर: रनवे पर एक इकाई परीक्षण के माध्यम से जेवीएम में होता है (यानी कोई रनटाइम त्रुटियाँ):
- निबंध में एक जोर है:
assert(a == b)
- टाइप-लेवल: कंपाइलर के माध्यम से कंपाइलर में होता है (यानी कोई कंपाइलर एरर नहीं):
- संक्षेप में एक प्रकार की तुलना है: उदाहरण के लिए
implicitly[A =:= B]
A <:< B
, संकलन करता है, केवल अगर A
इसका उप-प्रकार हैB
A =:= B
, संकलन करता है, केवल अगर A
इसका उप-प्रकार है B
और B
इसका उप-प्रकार हैA
A <%< B
, ("के रूप में देखने योग्य") केवल तभी A
देखा जा सकता है जब देखने योग्य हो B
(जैसे कि A
एक उप-प्रकार से एक अंतर्निहित रूपांतरण है B
)
- एक उदाहरण
- अधिक तुलना ऑपरेटरों
प्रकारों और मूल्यों के बीच रूपांतरण
कई उदाहरणों में, लक्षण के माध्यम से परिभाषित प्रकार अक्सर सार और सील दोनों होते हैं, और इसलिए न तो सीधे और न ही अनाम उपवर्ग के माध्यम से त्वरित किया जा सकता है। तो null
कुछ प्रकार के ब्याज का उपयोग करके मूल्य-स्तर की गणना करते समय प्लेसहोल्डर मूल्य के रूप में उपयोग करना आम है :
- उदाहरण के लिए
val x:A = null
, A
आप किस प्रकार की परवाह करते हैं
टाइप-इरेज़र के कारण, पैरामीटर किए गए प्रकार सभी समान दिखते हैं। इसके अलावा, (जैसा कि ऊपर उल्लेख किया गया है) आप जिन मूल्यों के साथ काम कर रहे हैं, वे सभी हो सकते हैं null
, और इसलिए वस्तु प्रकार पर कंडीशनिंग (जैसे एक मैच स्टेटमेंट के माध्यम से) अप्रभावी है।
चाल में निहित कार्यों और मूल्यों का उपयोग करना है। आधार मामला आमतौर पर एक निहित मूल्य होता है और पुनरावर्ती मामला आमतौर पर एक अंतर्निहित कार्य होता है। दरअसल, टाइप-लेवल प्रोग्रामिंग इम्पीकिट का भारी उपयोग करती है।
इस उदाहरण पर विचार करें ( मेटस्कला और सर्वनाश से लिया गया ):
sealed trait Nat
sealed trait _0 extends Nat
sealed trait Succ[N <: Nat] extends Nat
यहां आपके पास प्राकृतिक संख्याओं की एक पीनो एन्कोडिंग है। यही है, आपके पास प्रत्येक गैर-नकारात्मक पूर्णांक के लिए एक प्रकार है: 0 के लिए एक विशेष प्रकार, अर्थात् _0
; और शून्य से अधिक के प्रत्येक पूर्णांक में एक प्रकार का रूप होता है Succ[A]
, जहां एक प्रकार से A
छोटे पूर्णांक का प्रतिनिधित्व होता है। उदाहरण के लिए, 2 का प्रतिनिधित्व करने वाला प्रकार होगा: Succ[Succ[_0]]
(उत्तराधिकारी शून्य का प्रतिनिधित्व करने वाले प्रकार पर दो बार लागू होता है)।
हम अधिक सुविधाजनक संदर्भ के लिए विभिन्न प्राकृतिक संख्याओं को उर्फ कर सकते हैं। उदाहरण:
type _3 = Succ[Succ[Succ[_0]]]
(यह val
एक फ़ंक्शन के परिणाम के रूप में परिभाषित करने के लिए बहुत कुछ है ।)
अब, मान लीजिए कि हम एक मान-स्तरीय फ़ंक्शन को परिभाषित करना चाहते हैं, def toInt[T <: Nat](v : T)
जो एक तर्क मान में लेता है v
, जो कि Nat
पूर्णांक को प्राकृतिक v
प्रकार का प्रतिनिधित्व करने वाले पूर्णांक के रूप में देता है और देता है । उदाहरण के लिए, यदि हमारे पास मूल्य val x:_3 = null
( null
प्रकार का Succ[Succ[Succ[_0]]]
) है, तो हम चाहेंगेtoInt(x)
वापस लौटना3
।
लागू करने के लिए toInt
, हम निम्न वर्ग का उपयोग करने जा रहे हैं:
class TypeToValue[T, VT](value : VT) { def getValue() = value }
हम नीचे देखेंगे के रूप में, एक वस्तु वर्ग का निर्माण किया जाएगा TypeToValue
प्रत्येक के लिए Nat
से _0
ऊपर (जैसे) के लिए _3
, और प्रत्येक इसी प्रकार (यानी के मूल्य में प्रतिनिधित्व स्टोर करेगा TypeToValue[_0, Int]
मान संग्रहीत होगा 0
, TypeToValue[Succ[_0], Int]
मान संग्रहीत होगा 1
, आदि)। ध्यान दें, TypeToValue
दो प्रकारों से परिचालित किया जाता है: T
और VT
। T
उस प्रकार से मेल खाती है जिसे हम (हमारे उदाहरण में Nat
) मान निर्दिष्ट करने का प्रयास कर रहे हैं, और VT
उस प्रकार के मान से मेल खाते हैं जिसे हम इसे निर्दिष्ट कर रहे हैं (हमारे उदाहरण में,Int
) ।
अब हम निम्नलिखित दो निहित परिभाषाएँ बनाते हैं:
implicit val _0ToInt = new TypeToValue[_0, Int](0)
implicit def succToInt[P <: Nat](implicit v : TypeToValue[P, Int]) =
new TypeToValue[Succ[P], Int](1 + v.getValue())
और हम toInt
निम्नानुसार लागू करते हैं:
def toInt[T <: Nat](v : T)(implicit ttv : TypeToValue[T, Int]) : Int = ttv.getValue()
यह समझने के लिए कि कैसे toInt
काम करता है, आइए विचार करें कि यह कुछ इनपुट पर क्या करता है:
val z:_0 = null
val y:Succ[_0] = null
जब हम कॉल करते हैं toInt(z)
, तो कंपाइलर ttv
टाइप के एक निहित तर्क की तलाश करता है TypeToValue[_0, Int]
(क्योंकि z
टाइप का है _0
)। यह ऑब्जेक्ट ढूंढता है _0ToInt
, यह getValue
इस ऑब्जेक्ट की विधि को कॉल करता है और वापस हो जाता है0
। ध्यान देने वाली महत्वपूर्ण बात यह है कि हमने उस प्रोग्राम को निर्दिष्ट नहीं किया है जिसका उपयोग करने के लिए ऑब्जेक्ट को संकलित किया गया है।
अब विचार करते हैं toInt(y)
। इस बार, संकलक ttv
प्रकार के निहित तर्क के लिए लग रहा है TypeToValue[Succ[_0], Int]
(चूंकि y
प्रकार का है Succ[_0]
)। यह फ़ंक्शन को ढूंढता है succToInt
, जो उचित प्रकार ( TypeToValue[Succ[_0], Int]
) का ऑब्जेक्ट वापस कर सकता है और इसका मूल्यांकन कर सकता है। यह फ़ंक्शन अपने आप में एक अंतर्निहित तर्क ( v
) का प्रकार लेता है TypeToValue[_0, Int]
(अर्थात, TypeToValue
जहां पहला प्रकार का पैरामीटर एक कम है Succ[_]
)। संकलक आपूर्ति _0ToInt
(जैसा कि toInt(z)
ऊपर के मूल्यांकन में किया गया था ), और मूल्य के साथ succToInt
एक नई TypeToValue
वस्तु का निर्माण करता है 1
। फिर, यह ध्यान रखना महत्वपूर्ण है कि संकलक इन सभी मूल्यों को अंतर्निहित रूप से प्रदान कर रहा है, क्योंकि हमारे पास स्पष्ट रूप से उन तक पहुंच नहीं है।
अपने काम की जाँच कर रहा है
यह सत्यापित करने के कई तरीके हैं कि आपकी टाइप-स्तरीय गणनाएँ वह कर रही हैं जो आप अपेक्षा करते हैं। यहाँ कुछ दृष्टिकोण हैं। दो प्रकार बनाएं A
और B
, जिसे आप सत्यापित करना चाहते हैं, बराबर हैं। फिर जाँच करें कि निम्नलिखित संकलन:
Equal[A, B]
implicitly[A =:= B]
वैकल्पिक रूप से, आप टाइप को एक मान में बदल सकते हैं (जैसा कि ऊपर दिखाया गया है) और मानों का रनटाइम चेक करें। जैसे assert(toInt(a) == toInt(b))
, a
प्रकार A
का b
है और प्रकार का है B
।
अतिरिक्त संसाधन
उपलब्ध कंस्ट्रक्शन का पूरा सेट स्कैला संदर्भ मैनुअल (पीडीएफ) के प्रकार अनुभाग में पाया जा सकता है ।
Adriaan Moors प्रकार के निर्माणकर्ताओं और संबंधित विषयों के बारे में कई अकादमिक पत्र हैं, जिसमें scala के उदाहरण हैं:
एपोकैलिस्प एक ब्लॉग है जिसमें स्कैला में टाइप-स्तरीय प्रोग्रामिंग के कई उदाहरण हैं।
स्कालाज एक बहुत ही सक्रिय परियोजना है जो कार्यक्षमता प्रदान कर रही है जो विभिन्न प्रकार की प्रोग्रामिंग सुविधाओं का उपयोग करके स्काला एपीआई का विस्तार करती है। यह एक बहुत ही दिलचस्प परियोजना है जिसका एक बड़ा अनुसरण है।
मेटास्कला स्केला के लिए एक प्रकार-स्तरीय पुस्तकालय है, जिसमें प्राकृतिक संख्या, बूलियन, यूनिट, एचएलआईटीए आदि के लिए मेटा प्रकार शामिल हैं। यह एक परियोजना है, जोपर नोर्डबर्ग (उनके ब्लॉग) द्वारा बनाई गई है। ।
Michid (ब्लॉग) कुछ कमाल (अन्य जवाब से) स्काला में टाइप-स्तरीय प्रोग्रामिंग के उदाहरण हैं:
देबाशीष घोष (ब्लॉग) के पास कुछ प्रासंगिक पोस्ट हैं:
(मैं इस विषय पर कुछ शोध कर रहा हूं और यहां मैंने जो कुछ सीखा है, मैं अभी भी इसके लिए नया हूं, इसलिए कृपया इस उत्तर में किसी भी अशुद्धि को इंगित करें।)