अब तक सिलेबस में टाइप के बारे में अधिक नहीं कहा गया है, लेकिन साफ़ है कि Julia में ये मौजूद हैं:
julia> vals = (42, 4.3, π, "hello", 'Q')
(42, 4.3, π, "hello", 'Q')
julia> typeof(vals)
Tuple{Int64, Float64, Irrational{:π}, String, Char}
हमने टाइप कभी तय नहीं किए थे, फिर भी Julia ने उन्हें असाइन कर दिया।
types होते हैं, जो इसकी बनावट के केंद्र में हैं।Type Inference का इस्तेमाल करता है।JIT कंपाइलर पूरे कोड को देखता है, देखता है कि किसी वेरिएबल का इस्तेमाल कैसे हो रहा है, और उस इस्तेमाल के अनुकूल कोई उपयुक्त डिफ़ॉल्ट टाइप अनुमानित करता है।
पिछले कॉन्सेप्ट में ऐसे कई उदाहरण रहे हैं जिनमें संख्यात्मक टाइप को एकरूप बना दिया जाता है, और यह सबसे सरल अंकगणित से ही शुरू होता है।
julia> 2 + 1.3
3.3
हमने एक पूर्णांक (Int64) और एक दशमलव संख्या (Float64) जोड़ी और परिणाम Float64 आया। इसी तरह:
julia> nums = (3, 4.1, 1//4)
(3, 4.1, 1//4)
julia> typeof(nums)
Tuple{Int64, Float64, Rational{Int64}}
julia> [nums...]
3-element Vector{Float64}:
3.0
4.1
0.25
ऊपर के उदाहरण में टपल ने हर एलिमेंट का टाइप बनाए रखा, लेकिन वेक्टर में बदलने पर वे सब एक जैसे Float64 हो गए।
Julia का कंपाइलर समझता है कि कौन-से टाइप रूपांतरण संभव हैं: पूर्णांक से दशमलव संख्या में कोई दिक्कत नहीं, लेकिन दशमलव संख्या से पूर्णांक में जाने पर परिशुद्धता घट जाती है, इसलिए एक InexactError एरर आती है।
टाइप प्रोमोशन एक्सप्रेशन की सारी वैल्यू को एक ऐसे साझा टाइप में बदल देता है जो सभी इनपुट के साथ मेल खाने लायक लचीला होता है।
यही रूपांतरण promote() फंक्शन से साफ़-साफ़ भी किया जा सकता है:
julia> promote(nums...)
(3.0, 4.1, 0.25)
साधारण ट्यूटोरियल अभ्यास हल करने के लिए टाइप अनुमान और टाइप प्रोमोशन पर भरोसा करना ठीक है, लेकिन बड़े प्रोग्राम के लिए आपको अधिक सटीक नियंत्रण की ज़रूरत पड़ेगी।
अधिकांश आधुनिक प्रोसेसर पर पूर्णांक का डिफ़ॉल्ट टाइप Int64 होता है।
Numbers कॉन्सेप्ट में हमने देखा था कि किसी वैल्यू को डिफ़ॉल्ट से अलग किसी टाइप में बदला जा सकता है।
julia> x = Int16(42)
42
julia> typeof(x)
Int16
लेकिन वेरिएबल x में फिर भी किसी दूसरे टाइप की वैल्यू असाइन की जा सकती है:
julia> x = "changed"
"changed"
julia> typeof(x)
String
इसे type instability कहते हैं, यानी टाइप अस्थिरता। यह:
इसके बजाय हम :: ऑपरेटर का इस्तेमाल करके x का टाइप तय कर सकते हैं:
julia> y::Int16 = 42
42
julia> typeof(y)
Int16
julia> y = "changed"
ERROR: MethodError: Cannot `convert` an object of type String to an object of type Int16
The function `convert` exists, but no method is defined for this combination of argument types.
अब y का टाइप Int16 है, और हमेशा रहेगा।
इस तरह कंपाइलर जानता है कि इसके लिए कितने बाइट आरक्षित करने हैं, और बाकी कोड को इस भरोसे पर अनुकूलित कर सकता है कि टाइप स्थिर रहेगा।
इस मामले में यह वेरिएबल C जैसी स्टैटिक टाइपिंग वाली भाषा के वेरिएबल जैसा ही है।
ऊपर बताया गया टाइप असाइनमेंट किसी वेरिएबल असाइनमेंट के बाईं ओर इस्तेमाल होता है, ताकि उस वेरिएबल का टाइप तय हो जाए।
किसी वैल्यू के साथ, या ऐसी चीज़ के साथ जो मूल्यांकन करके कोई वैल्यू देती है, :: ऑपरेटर का इस्तेमाल आम तौर पर यह असेर्शन होता है कि वैल्यू इसी टाइप की होनी चाहिए, वरना एरर मिलनी चाहिए।
julia> 42::Number
42
julia> "two"::Number
ERROR: TypeError: in typeassert, expected Number, got a value of type String
आम तौर पर इसका इस्तेमाल किसी फंक्शन की रिटर्न वैल्यू के साथ किया जाता है, ताकि अंत में एक साधारण जाँच हो जाए कि फंक्शन ने वैसा ही काम किया जैसा अपेक्षित था।
ध्यान दीजिए कि असेर्शन के दूसरे रूपों के लिए @assert मैक्रो मौजूद है।
Int64, Int16, String, Char: ये टाइप वास्तव में "कहाँ से" आते हैं?
कई ऑब्जेक्ट-ओरिएंटेड (OO) भाषाओं में हर टाइप एक क्लास होता है, सबक्लास बनाकर उन्हें क्लास पदानुक्रम में सजाया जाता है, और क्लास मेथड उनका व्यवहार तय करते हैं।
Java और Ruby इस पैटर्न के साफ़ उदाहरण हैं, लेकिन भीतर से Python भी ऐसा ही है।
Julia में क्लास नहीं होती हैं।
दस्तावेज़ों में दिया गया कारण यह है कि OO की सुविधाएँ JIT कंपाइलर में रुकावट डालती हैं और रनटाइम प्रदर्शन घटा देती हैं।
कौन जाने, शायद उन्होंने यह उद्धरण भी पढ़ा हो:
"ऑब्जेक्ट-ओरिएंटेड प्रोग्रामिंग एक बेहद खराब विचार है, जिसका जन्म सिर्फ कैलिफोर्निया में ही हो सकता था।"
यह उद्धरण Edsger Dijkstra के नाम पर है, जो 1950 के दशक से कई दशकों तक एक शानदार कंप्यूटर वैज्ञानिक रहे (हालाँकि उन्हें आम तौर पर उनके उजले आशावाद या बारीक कूटनीति के लिए नहीं जाना जाता)।
और फिर भी, इस कोड को देखिए:
julia> y::Int16 = 42
42
julia> typeof(y)
Int16
julia> supertype(Int16)
Signed
julia> supertypes(Int16)
(Int16, Signed, Integer, Real, Number, Any)
अब कुछ बातें साफ़ करते हैं:
Int16 एक टाइप है, और हम इस टाइप के वेरिएबल बना सकते हैं।Int16, Signed का सबटाइप है, और supertype() फंक्शन हमें यही दिखाता है।Integer, Real और Number से होकर ऊपर Any तक जाता है, और subtypes() हमें पदानुक्रम की यह शाखा दिखा देता है।सारी शाखाएँ Any पर जाकर खत्म होती हैं, और Any इस मामले में अलग है कि वह खुद अपना सुपरटाइप है।
julia> supertypes(String)
(String, AbstractString, Any)
julia> supertype(Any)
Any
तो Julia में class का पदानुक्रम नहीं है, लेकिन type का पदानुक्रम ज़रूर है।
पूरा पदानुक्रम दिखाने की कोशिश करने पर एक बहुत बड़ा ट्री मिलता है, जिसे ठीक से देखना संभव ही नहीं है।
पदानुक्रम के कुछ हिस्सों को देखने की बात ऑनलाइन चर्चा में उठाई गई है।
आगे चलकर हम यह समझने की कोशिश करेंगे कि यह कैसे काम करता है, लेकिन पहले अभी बहुत कुछ खोजना बाकी है।
आम तरीके से बराबरी जाँचने के लिए हम typeof() का इस्तेमाल कर सकते हैं।
julia> typeof(11)
Int64
julia> typeof(11) == Int64
true
julia> typeof(11) == Number
false
टाइप की बराबरी बिल्कुल सटीक होनी चाहिए, क्योंकि तुलना का यह तरीका टाइप पदानुक्रम को नहीं समझता।
इससे अधिक लचीले ढंग से, isa बताता है कि किसी वैल्यू का टाइप तुलना करने वाले टाइप जैसा ही है या उसका सबटाइप है।
इसका इस्तेमाल इन्फिक्स या फंक्शन, दोनों रूपों में किया जा सकता है।
julia> 12 isa Int64
true
julia> 12 isa Number
true
julia> isa(12, Number)
true
julia> 12 isa String
false
ध्यान दीजिए कि isa बाईं ओर एक value की अपेक्षा करता है, किसी type की नहीं।
इस तरीके से दो टाइप की तुलना करने पर अनपेक्षित परिणाम मिलेंगे।
सही ऑपरेटर <: है, जिसे हम आगे के कॉन्सेप्ट में और बहुत देखेंगे।
julia> Int64 isa Number ## Don't do this!
false
julia> Int64 <: Number
true
टाइप की जाँच का इस्तेमाल किसी फंक्शन के भीतर फ़्लो नियंत्रित करने के लिए किया जा सकता है, लेकिन स्वाभाविक Julia कोड में यह अपेक्षाकृत असामान्य है।
मल्टीपल डिस्पैच कॉन्सेप्ट में हम देखेंगे कि फंक्शन के आर्गुमेंट में टाइप जोड़ना और ऐसी तर्क-प्रक्रिया Julia के डिस्पैच तंत्र पर छोड़ देना अक्सर अधिक कुशल होता है। लेकिन मल्टीपल डिस्पैच तक पहुँचने से पहले टाइप से जुड़े कई और कॉन्सेप्ट हैं जिन पर चर्चा करनी है।
हमने देखा कि टाइप पदानुक्रम एक ट्री संरचना बनाता है (कंप्यूटर विज्ञान के अर्थ में, जिसकी जड़ सबसे ऊपर होती है)।
ट्री में हर चीज़ एक node होती है, और इन्हें श्रेणियों में बाँटा जा सकता है:
abstract कहा जाता है।concrete कहा जाता है।julia> subtypes(Integer) # an abstract type
3-element Vector{Any}:
Bool
Signed
Unsigned
julia> subtypes(Int64) # a concrete type
Type[]
यह फर्क अहम है, क्योंकि सिर्फ मूर्त टाइप को ही वेरिएबल के रूप में instantiated किया जा सकता है।
julia> a::Int16 = 42
42
julia> typeof(a)
Int16
julia> b::Integer = 42
42
julia> typeof(b)
Int64
ध्यान दीजिए कि अमूर्त टाइप इस्तेमाल करने पर (इस मामले में) कोई एरर संदेश नहीं मिलता, बल्कि कंपाइलर खुद एक उपयुक्त मूर्त टाइप बना देता है: Integer की जगह Int64।
कम से कम इससे यह तो होता है कि वेरिएबल में गैर-पूर्णांक वैल्यू असाइन नहीं की जा सकती:
julia> f::Integer = "hello"
ERROR: MethodError: Cannot `convert` an object of type String to an object of type Integer
The function `convert` exists, but no method is defined for this combination of argument types.
इस तरह अमूर्त टाइप के साथ किया गया टाइप असाइनमेंट वेरिएबल के टाइप पर एक प्रतिबंध लगाता है: वह (ऊपर के मामले में) Integer का कोई भी सबटाइप हो सकता है।
प्रोग्रामिंग भाषाओं की दुनिया में यह असामान्य बात है: यह C के टाइप असाइनमेंट से कमज़ोर है, लेकिन Python के हाल के संस्करणों में मिलने वाले टाइप संकेत से मज़बूत है।
isabstracttype() और isconcretetype() फंक्शन से यह जाँचा जा सकता है।
ध्यान दीजिए कि ये दोनों एक-दूसरे का उल्टा भर नहीं हैं: किसी आगे के कॉन्सेप्ट में हम देखेंगे कि कुछ टाइप न अमूर्त होते हैं, न मूर्त।
julia> isconcretetype(Integer), isabstracttype(Integer)
(false, true)
julia> isconcretetype(Int64), isabstracttype(Int64)
(true, false)
# Vector is neither
julia> isconcretetype(Vector), isabstracttype(Vector)
(false, false)
हालाँकि Vector मूर्त टाइप नहीं है, फिर भी उसके एलिमेंट मूर्त होते हैं।
eltype() (एलिमेंट टाइप) फंक्शन यह टाइप निकालकर देता है:
julia> eltype([1, 2.3])
Float64