تا اینجا، برنامهی درسی چیز زیادی دربارهی نوعها نگفته است، اما بدیهی است که در جولیا وجود دارند:
julia> vals = (42, 4.3, π, "hello", 'Q')
(42, 4.3, π, "hello", 'Q')
julia> typeof(vals)
Tuple{Int64, Float64, Irrational{:π}, String, Char}
ما هیچوقت نوعها را مشخص نکردیم، اما جولیا بههرحال آنها را تعیین کرد.
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 هم در درون شبیه همین است.
جولیا کلاس ندارد.
دلیل مستندشده این است که ویژگیهای شیءگرایی با کامپایلر 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
پس جولیا سلسلهمراتب 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.
در این تمرین میخواهید کمی مهندسی داده انجام دهید و دادههای نمرهدهی را پیشپردازش کنید. مدرسی با دانشآموزان بسیار، بسیار، بسیار (شاید هم زیادی؟) میخواهد تحلیلی روی نمرههای دانشآموزان انجام دهد و قصد دارد این کار را به شکلی کارآمد انجام دهد. پس هدف، کمحجم کردن و مرتب کردن دادههاست.
مقیاس نمرهدهی معمولاً از ۰ تا ۱۰ است، پس کارآمدترین راه استفاده از نوع دادهای است که برای هر نمره فقط هشت بیت داشته باشد.
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 برگرداند.MethodError پرتاب کنند.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 باید همهی اعداد را تنزل دهد و یک بردار مرتبشده به ترتیب نزولی برگرداند.MethodError پرتاب کنند.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