نو

نوع در Julia

1 تمرین

درباره‌ی نوع

تا اینجا، سرفصل چیز زیادی درباره‌ی انواع نگفته است، اما آشکارا در Julia وجود دارند:

julia> vals = (42, 4.3, π, "hello", 'Q')
(42, 4.3, π, "hello", 'Q')

julia> typeof(vals)
Tuple{Int64, Float64, Irrational{:π}, String, Char}

ما هیچ‌وقت انواع را مشخص نکردیم، اما Julia به‌هرحال آن‌ها را تعیین کرد.

  1. Julia types دارد که در طراحی آن نقشی محوری دارند.
  2. Julia معمولاً می‌تواند نوع را «حدس بزند»، با استفاده از 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 کلاس ندارد.

دلیل مکتوب آن این است که ویژگی‌های شیءگرایی با کامپایلر JIT تداخل می‌کنند و به کارایی زمان اجرا آسیب می‌زنند.

چه می‌دانیم، شاید آن‌ها هم این نقل‌قول را خوانده بودند:

«برنامه‌نویسی شیءگرا ایده‌ی فوق‌العاده بدی است که فقط می‌توانست در کالیفرنیا پدید بیاید.»

این جمله به Edsger Dijkstra نسبت داده می‌شود؛ دانشمند رایانه‌ی درخشانی که از دهه‌ی ۱۹۵۰ چندین دهه فعال بود (هرچند معمولاً به خوش‌بینی سرزنده یا دیپلماسی زیرکانه‌اش شناخته نمی‌شود).

و با این حال، به این کد نگاه کنید:

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

بررسی انواع می‌تواند برای کنترل جریان درون یک تابع به کار رود، اما این کار در Julia متعارف نسبتاً غیرمعمول است.

در مفهوم ارسال چندگانه خواهیم دید که اغلب کارآمدتر است که به آرگومان‌های تابع نوع اضافه کنیم و بگذاریم سازوکار ارسال Julia چنین منطقی را مدیریت کند. اما پیش از رسیدن به ارسال چندگانه، چند مفهوم دیگر مرتبط با نوع هست که باید درباره‌شان بحث کنیم.

انواع انتزاعی در برابر انواع عینی

دیدیم که سلسله‌مراتب انواع یک ساختار درختی می‌سازد (به معنای علوم رایانه، با ریشه در بالا).

هر عنصر درخت یک node است و این‌ها را می‌توان به دسته‌هایی تقسیم کرد:

  1. گره‌هایی که زیرنوع دارند abstract نامیده می‌شوند.
  2. گره‌های برگ، که زیرنوعی ندارند، 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. حداقل جلوی انتساب متغیر به چیزی غیر از عدد صحیح را می‌گیرد:

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
ویرایش از طریق GitHub این پیوند در پنجره یا زبانه‌ی جدیدی باز می‌شود

نوع را یاد بگیرید