একটি Tuple অনেক দিক থেকেই Vector (একমাত্রিক array)-এর সাথে বেশ মিল। তবে কয়েকটি গুরুত্বপূর্ণ পার্থক্যও আছে।
[ ]-এর বদলে প্রথম বন্ধনী ( )-এর ভেতরে, যদিও অর্থ স্পষ্ট থাকলে প্রথম বন্ধনী বাদ দেওয়া যায়।ইমিউটেবিলিটির সুবাদে কম্পাইলার আরও বেশি অপটিমাইজেশন করতে পারে এবং টুপলগুলো ভেক্টরের চেয়ে বেশি পারফরম্যান্ট হয়, তাই যেখানে মিউটেবিলিটির দরকার নেই সেখানে টুপলকেই প্রাধান্য দেওয়া উচিত।
julia> t = (3, 5.2, "xyz")
(3, 5.2, "xyz")
# Non-homogeneous:
julia> dump(t)
Tuple{Int64, Float64, String}
1: Int64 3
2: Float64 5.2
3: String "xyz"
julia> t[2]
5.2
# Immutable:
julia> t[2] = 7.3
ERROR: MethodError: no method matching setindex!(::Tuple{Int64, Float64, String}
Tuple() নামের একটি কনস্ট্রাক্টরও আছে, যা যেকোনো ইটারেবল কালেকশনকে টুপলে পরিণত করে। দরকারি ব্যাপার হলো, এটি একটি স্ট্রিংকে ক্যারেক্টারের ইটারেবল কালেকশন হিসেবে ধরে।
julia> Tuple("Julia")
('J', 'u', 'l', 'i', 'a')
যেকোনো টুপলের ক্ষেত্রে ইনডেক্স দিয়ে এলিমেন্ট অ্যাক্সেস করা যায়, আবার সেগুলোর উপর লুপও চালানো যায়।
শুধু সংখ্যা দিয়ে গঠিত যেকোনো টুপল অ্যারেগুলোর মতোই sum()-এর মতো গাণিতিক ফাংশনে ব্যবহার করা যায়।
মাল্টিপল অ্যাসাইনমেন্ট ব্যবহার করে একটি টুপলের প্রতিটি এলিমেন্ট আলাদা করে আনপ্যাক করা যায়। যে এলিমেন্টগুলোর দরকার নেই, সেগুলোর জন্য ডামি ভ্যারিয়েবল হিসেবে আন্ডারস্কোর _ ব্যবহার করুন।
julia> tup = (1, "Julia", true)
(1, "Julia", true)
julia> num, name, _ = tup
(1, "Julia", true)
julia> uppercase(name), 2num
("JULIA", 2)
একটি টুপলের সব এলিমেন্ট অন্য একটি টুপলে (বা সাধারণভাবে অন্য একটি কালেকশনে) কপি করতে splat অপারেটর ... দিয়ে সেটি আনপ্যাক করা যায়। ফাংশন কনসেপ্টে এটি আরও বিস্তারিতভাবে আলোচনা করা হবে।
julia> new_tup = (tup..., π, '😊')
(1, "Julia", true, π, '😊')
একটি Tuple শুধু মান ধারণ করে। NamedTuple প্রতিটি মানের সাথে একটি নাম জুড়ে দেয়।
এরপর "ডট" নোটেশন দিয়ে প্রতিটি ফিল্ড আলাদাভাবে অ্যাক্সেস করা যায়:
julia> nt = (a = 1, b = 7.3)
(a = 1, b = 7.3)
julia> dump(nt)
@NamedTuple{a::Int64, b::Float64}
a: Int64 1
b: Float64 7.3
julia> nt.b
7.3
নামটি ভেতরে Symbol হিসেবে সংরক্ষিত থাকে (এটি একটি ইমিউটেবল টাইপ, আর নামটির আগে কোলন : বসানো হয়), তাই বিকল্প একটি সিনট্যাক্সও ব্যবহার করা যায়:
julia> nt[:b]
7.3
নেমড টুপল তৈরি করার কাজটি কখনও কখনও দুটি ধাপে ভাগ করা সুবিধাজনক:
# Step 1
julia> Student = NamedTuple{(:name, :grade), Tuple{String, Int}}
@NamedTuple{name::String, grade::Int64}
julia> typeof(Student)
DataType
# Step 2
julia> s = Student( ("Helen", 3) )
(name = "Helen", grade = 3)
julia> dump(s)
@NamedTuple{name::String, grade::Int64}
name: String "Helen"
grade: Int64 3
নেমড টুপল তৈরির আরও অবাক করার মতো অনেক উপায় আছে, তাই আগ্রহ থাকলে ডকুমেন্টেশন দেখে নিন।
নামগুলো (এগুলোকে কী (key)-ও বলা হয়) বা মানগুলো আলাদাভাবে পাওয়া সহজ:
julia> keys(s)
(:name, :grade)
julia> values(s)
("Helen", 3)
মনে রাখবেন, একটি নেমড টুপলের উপর ইটারেশন করলে কেবল মানগুলো পাওয়া যায়, আর নামগুলো উপেক্ষিত থাকে।
ইটারেশনে নামগুলোও যদি কাজে লাগে, তবে আরও কিছু ধাপ প্রয়োজন। NamedTuple-কে Pairs-এর একটি Vector-এ রূপান্তর করা যায় (Pair নিয়ে আলোচনা অন্য একটি কনসেপ্টে)।
julia> pairs(s)
pairs(::NamedTuple) with 2 entries:
:name => "Helen"
:grade => 3
julia> collect(pairs(s))
2-element Vector{Pair{Symbol, Any}}:
:name => "Helen"
:grade => 3
সুতরাং নেমড টুপলকে অনেকটা ডিকশনারির মতোই বলা যায়। তবে জুলিয়ার একটি [Dict][dict]-এর সাথে এর কয়েকটি গুরুত্বপূর্ণ পার্থক্য আছে:
Dict মিউটেবল; আর NamedTuple অন্য যেকোনো টুপলের মতোই ইমিউটেবল।Dict কোন কী (key) টাইপ গ্রহণ করবে তা নিয়ে নমনীয়; NamedTuple কেবল সিম্বলকেই কী (key) হিসেবে ব্যবহার করতে পারে।টুপল, নেমড হোক বা না হোক, ফাংশনে ও ফাংশন থেকে স্ট্রাকচার্ড ডেটা আদান-প্রদানের খুব কমন একটি উপায়। এই ক্ষেত্রে ইমিউটেবিলিটি ভালো, কারণ এটি ডেটার নিরাপত্তা দেয় এবং কম্পাইলারকে আরও জোরালো অপটিমাইজেশন করার সুযোগ দেয়।