حتى الآن، لم يقل المنهج الكثير عن الأنواع، لكنها موجودة بوضوح في 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 الكود (كله)، ويرى كيف يُستخدم المتغير، ثم يستنتج نوعًا افتراضيًا مناسبًا يتوافق مع ذلك الاستخدام.
لا بأس من الاعتماد على استنتاج الأنواع لحل تمارين تعليمية بسيطة، لكنك في البرامج الأكبر ستحتاج على الأرجح إلى تحكم أكثر دقة.
في معظم المعالجات الحديثة، يكون العدد الصحيح افتراضيًا من النوع 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.
Int64، Int16، String، Char: من أين "تأتي" هذه الأنواع؟
في كثير من اللغات كائنية التوجه (OO)، يكون كل نوع صنفًا، وترتّب الأصنافُ الفرعية الأصنافَ في تسلسل هرمي، وتحدّد طرق الصنف السلوكيات.
و Java و Ruby مثالان واضحان على هذا النمط، بل حتى Python مشابهة من الداخل.
لا تمتلك Julia أصنافًا.
السبب الموثّق هو أن ميزات البرمجة كائنية التوجه تتعارض مع مصرّف JIT وتضر بأداء وقت التشغيل.
ومع ذلك، انظر إلى هذا الكود:
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، وهو فريد في كونه النوع الأعلى لنفسه.
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
رأينا أن التسلسل الهرمي للأنواع يشكّل بنية شجرية (بالمعنى الحاسوبي، مع الجذر في القمة).
كل عنصر في الشجرة هو 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
لاحظ أن محاولة استخدام نوع مجرد لا تعطي رسالة خطأ (في هذه الحالة)، لكن المصرّف ينشئ نوعًا ملموسًا مناسبًا: Int64 بدل Integer.
في هذا التمرين، ستقوم بقليل من هندسة البيانات عبر المعالجة المسبقة لبعض بيانات الدرجات. تحاول مدرسة تضم عددًا كبيرًا جدًا (بل مفرط؟) من الطلاب إجراء بعض تحليل البيانات على الدرجات التي حصل عليها الطلاب، وتسعى إلى إنجاز ذلك بكفاءة، لذا فالفكرة هي تقليل البيانات وتنظيمها.
عادةً ما يكون سلّم الدرجات من 0 إلى 10، لذا فالأكثر كفاءة هو استخدام نوع بيانات لا يتجاوز ثماني بتات لكل درجة.
UInt8.Int8.بعد أن تنفّذ الدالة التي تحوّل الدرجات، ستحتاج إلى كتابة دالة أخرى للتعامل مع المجموعات التي تُحفظ فيها، وذلك بخفض الدرجات وإرجاعها بترتيب تنازلي.
Vector مرتبة ترتيبًا تصاعديًا لتخزين درجاتهم، لأنهم دقيقون في بياناتهم.Set غير مرتبة لتخزين درجاتهم، لأنهم أكثر تكاسلًا قليلًا.في كلتا الدالتين، ستحتاج إلى التعامل مع المُدخلات غير الصالحة عن طريق رمي MethodError.
ستُغطّى معالجة الاستثناءات في مفهوم لاحق، لذا، ولأغراض هذا التمرين، يمكنك استخدام الصيغة التالية:
throw(MethodError(f, args))
حيث f هي الدالة، وargs هو صف يحتوي على الوسائط المُدخلة إلى الدالة.
وفي حالة الدالة demote(n)، فإن f هي demote وargs هو (n,):
throw(MethodError(demote, (n,)))
نفّذ الطريقة demote(n).
Float64، ينبغي أن تقرّب إلى أقرب عدد صحيح وتُرجع قيمة من نوع البيانات UInt8.Integer، ينبغي أن تُرجع العدد الصحيح نفسه من نوع البيانات Int8.julia> demote(4.2)::UInt8
5
julia> demote(4)::Int8
4
julia> demote("hi")
MethodError: no method matching demote(::String) #output truncated
نفّذ الطريقة preprocess(coll).
Vector، ينبغي أن تخفّض جميع الأعداد وتعكس ترتيب المصفوفة.Set، ينبغي أن تخفّض جميع الأعداد وتُرجع مصفوفة مرتبة ترتيبًا تنازليًا.julia> preprocess([1, 2, 3])
3-element Vector{Int8}:
3
2
1
julia> preprocess(Set([2.2, 5.8, 3.4]))
3-element Vector{UInt8}:
6
4
3
julia> preprocess(42)
MethodError: no method matching preprocess(::Int64) #output truncated
سجّل في Exercism لتتعلّم وتتقن Julia عبر 35 مفهومًا128 تمرينًا، وإرشاد بشري حقيقي، وكل ذلك مجانًا.