লেন্স ব্যবহার করে নেস্টেড রেকর্ড আপডেট করুন (ইমিউটেবল ডেটাযুক্ত ভাষার জন্য প্রযোজ্য)।
নেস্টেড, ইমিউটেবল রেকর্ডের ফিল্ড আপডেট করা বেশ বিরক্তিকর একটি কাজ। এমন ক্ষেত্রে স্ট্রাকচার যতটা গভীর, কোডও ততটাই জটিল হয়ে পড়ে। ধরুন আপনার একটি Person আছে, যার ভেতরে একটি Address আছে, সেই Address-এ একটি Street আছে, আর সেই Street-এ একটি Number আছে। এখন Number আপডেট করতে হলে নতুন Number দিয়ে একটি নতুন Street বানাতে হবে, তারপর সেই নতুন Street দিয়ে একটি নতুন Address, আর শেষে সেই নতুন Address দিয়ে একটি নতুন Person। এতেই ঘুলিয়ে গেলেন?
এই সমস্যার একটি সমাধান হলো লেন্স ব্যবহার করা।
লেন্স ব্যবহার করে রেকর্ড অ্যাক্সেস করার কয়েকটি ফাংশন ইমপ্লিমেন্ট করুন। টেস্ট স্যুট আপনাকে লেন্স পুরোপুরি এড়িয়ে যাওয়ার সুযোগও দেয়, যাতে আপনি ভিন্ন ভিন্ন পদ্ধতি নিয়ে পরীক্ষা-নিরীক্ষা করতে পারেন।
Roc-এ লেন্স খুব একটা ব্যবহার হয় না, কারণ রেকর্ড আপডেট সিনট্যাক্স বেশিরভাগ ক্ষেত্রেই সুন্দরভাবে কাজ চালিয়ে দেয়। উদাহরণস্বরূপ, এই নেস্টেড রেকর্ডটি দেখুন:
bob = { hair: { color: Brown, length: Long }, mouth: Smiling, height: 6.0 }
এখন চলুন বব-এর চুল কেটে দিই:
new_bob = { ..bob, hair: { ..bob.hair, length: Short } }
লেন্সের কোনো দরকারই নেই! তবে Roc-এ লেন্স বিরল হলেও Haskell আর Scala-র মতো অন্য ভাষায় এগুলো বেশ কমন, তাই Roc-এ কীভাবে এগুলো ইমপ্লিমেন্ট করা যায় তা জানতে আপনার কৌতূহল হতে পারে। এছাড়া প্যারামিটারাইজড টাইপ আর ফাংশন ধারণ করা রেকর্ডের মতো Roc-এর কিছু অ্যাডভান্সড ফিচার চর্চা করার জন্যও এটি একটি ভালো উপায়। প্রকৃতপক্ষে, Roc-এ একটি লেন্স প্রকাশ করার একটি উপায় এখানে দেওয়া হলো:
Lens(whole, part) := {
get : whole -> part,
set : whole, part -> whole,
}
এটি একটি প্যারামিটারাইজড টাইপ: whole আর part যেকোনো টাইপ হতে পারে। whole সাধারণত একটি রেকর্ড হয়, আর part যেকোনো ফিল্ড টাইপ হতে পারে। get ফিল্ডে থাকে এমন একটি ফাংশন, যা দেওয়া whole-এর একটি নির্দিষ্ট part রিটার্ন করে, আর set ফিল্ডে থাকে এমন একটি ফাংশন, যা দেওয়া whole-এর একটি কপি রিটার্ন করে, তবে সেখানে একটি নির্দিষ্ট part-কে দেওয়া part দিয়ে বদলে দেওয়া হয়।
উদাহরণস্বরূপ, একজন মানুষের চুল পাওয়া বা সেট করার একটি লেন্স এখানে দেওয়া হলো:
hair_lens = {
get: |person| person.hair,
set: |person, hair| { ..person, hair },
}
এই লেন্স ব্যবহার করে আমরা বব-এর চুলে ফোকাস করতে পারি, এভাবে:
bob_hair = bob |> hair_lens.get
আর এভাবে আমরা তাঁর চুল বদলাতে পারি:
new_hair = { ..bob.hair, length: Short }
new_bob = bob |> (hair_lens.set)(new_hair)
লক্ষ্য করুন: hair_lens.set-এর চারপাশের বন্ধনীগুলো জরুরি: এগুলো ছাড়া Roc hair_lens-এর টাইপে set নামের একটি ফাংশন খুঁজতে থাকবে।
লেন্সগুলো কম্পোজ করা শুরু করলে এগুলো বিশেষভাবে কাজে আসে, যা আপনাকে গভীরভাবে নেস্টেড অংশগুলো পাওয়া ও সেট করার সুযোগ দেয়। এ রকম একটি ফাংশন ইমপ্লিমেন্ট করে এটি করা যায়:
compose : Lens(outer, inner), Lens(inner, part) -> Lens(outer, part)
এই ফাংশনটি ইমপ্লিমেন্ট হয়ে গেলে, এটি ব্যবহার করে এভাবে একটি চুলের দৈর্ঘ্যের লেন্স বানাতে পারবেন:
length_lens = {
get: |hair| hair.length,
set: |hair, length| { ..hair, length },
}
hair_length_lens = compose(hair_lens, length_lens)
এখন আপনি বব-এর আরেকবার চুল কেটে দিতে পারবেন:
set_hair_length = hair_length_lens.set
new_bob = bob |> set_hair_length(Short)
আপনি এমন একটি ফাংশনও ইমপ্লিমেন্ট করতে চাইতে পারেন, যা আপনাকে যেকোনো নেস্টেড অংশে যেকোনো রূপান্তর প্রয়োগ করতে দেয়:
over : Lens(whole, part), whole, (part -> part) -> whole
ইমপ্লিমেন্ট হয়ে গেলে, এভাবে over ব্যবহার করতে পারবেন:
cut_hair = |current_length| match current_length {
Long => Short
Short => Bald
Bald => Bald
}
new_bob = hair_length_lens |> over(bob, cut_hair)
এই অনুশীলনীতে, টেস্টগুলো যাচাই করবে যে আপনার কোড নিচের নেস্টেড রেকর্ডটির বিভিন্ন নেস্টেড অংশ পরিবর্তন করতে পারে কি না:
test_person : Person
test_person = {
name: { forenames: "Jane Joanna", surname: "Doe" },
birth: {
born_at: { street: "Longway", house_number: 1024, place: "Springfield", country: "United States" },
born_on: { year: 1984, month: 4, day: 12 },
},
address: { street: "Shortlane", house_number: 2, place: "Fallmeadow", country: "Canada" },
}
আমরা সুপারিশ করি, প্রথমে Roc-এর রেকর্ড আপডেট সিনট্যাক্স ব্যবহার করে টেস্টগুলো পাস করুন, তারপর বদলে লেন্স ব্যবহার করে দেখুন।
Exercism-এ সাইন আপ করুন, Roc ট্র্যাকের 125টি অনুশীলনী আর সত্যিকারের মানুষের মেন্টরিং দিয়ে শিখুন ও দক্ষ হয়ে উঠুন, সম্পূর্ণ বিনামূল্যে।