Elm-এর টাইপগুলো জেনেরিক টাইপ সমর্থন করে, যা সাধারণত একটি ইন্টারফেসে নমনীয়তা বাড়ায়।
যেমন, Maybe a টাইপটি যেকোনো টাইপের একটি মান ধারণ করতে পারে।
type Maybe a = Nothing | Just a
লক্ষ্য করুন, উপরের উদাহরণে টাইপ প্যারামিটার a সমান চিহ্ন =-এর দুই দিকেই ব্যবহৃত হয়েছে।
আমরা বলি যে a টাইপ ডেফিনিশনের ভেতরের কিছু ডেটার সাথে বাঁধা।
তবে কিছু ক্ষেত্রে টাইপ প্যারামিটারটি কেবল সমীকরণের বাম দিকেই দেখা যায়।
type Distance unit = Distance Float
উপরের Distance-এর ডেফিনিশনে unit একটি ফ্রি টাইপ প্যারামিটার, এটি টাইপের ভেতরের কোনো ডেটার সাথে বাঁধা নয়।
আমরা এই টাইপকে ফ্যান্টম টাইপ-ও বলতে পারি।
কম্পাইল টাইমে সীমাবদ্ধতা প্রয়োগ করতে চাইলে এটি আশ্চর্যজনকভাবে কাজে লাগে। যেমন, আমরা নিশ্চিত করতে চাই যেন কেবল একই ইউনিটের দূরত্বই যোগ করি।
-- Distance is an opaque type, since the module does not expose its variants.
-- This means that users may only use the functions meter, foot and add to manipulate distances.
module Distance exposing (Distance, Meter, Foot, meter, foot, add)
-- The Distance type has a phantom type 'unit'.
type Distance unit = Distance Float
-- We define two types that will be used in place
-- of the phantom type 'unit' in our constructor functions.
type Meter = Meter
type Foot = Foot
-- Constructor for Meter
meter : Distance Meter
meter = Distance 1.0
-- Constructor for Foot
foot : Distance Foot
foot = Distance 0.3048
-- The add function cannot take two parameters of different types.
-- So we cannot add meters and feet by mistake.
add : Distance unit -> Distance unit -> Distance unit
add (Distance d1) (Distance d2) = Distance (d1 + d2)
ওই মডিউলের বাইরের কোড Distance টাইপের ভেতরের কিছু অ্যাক্সেস করতে পারে না।
ব্যবহারকারীরা দুটি Distance Meter যোগ করতে পারেন, কিন্তু একটি Distance Meter এবং একটি Distance Foot একসাথে যোগ করতে পারেন না।
import Distance exposing (Distance)
-- Compiles
twoMeters = Distance.add Distance.meter Distance.meter
-- Does not compile
errDist = Distance.add Distance.meter Distance.foot
কোন টাইপ ফ্যান্টম হতে পারে তা নিয়ে কোনো নিষেধাজ্ঞা নেই, রেকর্ড টাইপও ব্যবহার করা যায়। ফ্যান্টম টাইপ হিসেবে ব্যবহার করলে রেকর্ডগুলো জটিল সীমাবদ্ধতা প্রকাশ করতে পারে, যা ফাংশনের মধ্য দিয়ে রূপান্তরিত হতে পারে।
চলুন Distance মডিউলের ভেতরে LegoBlock নামে একটি নতুন দূরত্বের ইউনিট যোগ করি।
নিয়ন্ত্রিত বস্তু হওয়ায় ভৌত ব্লকের সবসময় দুটি বৈশিষ্ট্য থাকে: তাদের দূরত্ব ভগ্নাংশহীন এবং ঋণাত্মক নয়।
type LegoBlock = LegoBlock
fourStuds : Distance { properties | unit: LegoBlock, nonFractional : (), nonNegative: () }
fourStuds = Distance 4.0
nonFractional আর nonNegative এমন একটি রেকর্ডের ফিল্ড, যা টাইপ আর্গুমেন্টের বাইরে কোথাও নেই, তাই এদের () টাইপ দেওয়াই মানানসই, যাকে ইউনিট টাইপ বলা হয় এবং যা কেবল নিজের উপস্থিতি ছাড়া আর কোনো তথ্য ধারণ করে না।
ইচ্ছেমতো LegoBlock দূরত্ব পাওয়া অবশ্যই সম্ভব, যেমন দূরত্বের পার্থক্য বা অনুপাত হিসাব করার পরে।
negativeStud : Distance { properties | unit: LegoBlock, nonFractional : () }
negativeStud = Distance -1.0
threeFiddyStud : Distance { properties | unit: LegoBlock, nonNegative : () }
threeFiddyStud = Distance 3.50
crazyStud : Distance { properties | unit: LegoBlock }
crazyStud = Distance -13.37
উপরের সব মানই বৈধ এবং কম্পাইল হবে, তবে fourStuds-এর মতো মানগুলো আমাদের কাছে বিশেষ আগ্রহের, কারণ এদের মধ্যে nonFractional আর nonNegative দুটি বৈশিষ্ট্যই আছে, আর এগুলো এমন ভৌত ব্লক বোঝায় যেগুলো একসাথে মেলানো যায়:
combineLegoBlocks
: Distance { properties | unit: LegoBlock, nonFractional : (), nonNegative: () }
-> Distance { properties | unit: LegoBlock, nonFractional : (), nonNegative: () }
-> Distance { properties | unit: LegoBlock, nonFractional : (), nonNegative: () }
combineLegoBlocks = add
চলুন কিছু ফাংশন যোগ করি, যাতে ব্যবহারকারীরা LegoBlock দূরত্ব তৈরি ও পরিমার্জন করতে পারেন।
newLegoBlock : Float -> Distance { properties | unit: LegoBlock }
newLegoBlock dist = Distance dist
floorDistance : Distance properties -> Distance { properties | nonFractional : () }
floorDistance (Distance dist) = Distance (toFloat (floor dist))
ceilingDistance : Distance properties -> Distance { properties | nonFractional : () }
ceilingDistance (Distance dist) = Distance (toFloat (ceiling dist))
absDistance : Distance properties -> Distance { properties | nonNegative : () }
absDistance (Distance dist) = Distance (abs dist)
লক্ষ্য করুন, floorDistance, ceilingDistance আর absDistance LegoBlock ছাড়াও অন্য ইউনিট সামলাতে পারে, এবং সাধারণভাবে ইনপুটের বৈশিষ্ট্য নিয়ে কোনো অনুমান করে না, এরা কেবল নিশ্চিত করে যে আউটপুটে একটি নির্দিষ্ট বৈশিষ্ট্য থাকবে, সেটি nonFractional হোক বা nonNegative।
চলুন কিছু ফলাফল দেখি।
import Distance exposing (Distance)
-- Compiles
distance1 = combineLegoBlocks fourStuds fourStuds
-- Does not compile
distance2 = combineLegoBlocks fourStuds threeFiddyStud
-- Compiles
distance3 = combineLegoBlocks fourStuds (floorDistance threeFiddyStud)
-- Does not compile
distance4 = combineLegoBlocks fourStuds negativeStud
-- Compiles
distance5 = combineLegoBlocks fourStuds (absDistance negativeStud)
-- Does not compile
distance6 = combineLegoBlocks fourStuds crazyStud
-- Compiles
distance7 = combineLegoBlocks fourStuds (floorDistance (absDistance crazyStud))
-- Compiles
distance8 = combineLegoBlocks fourStuds (ceilingDistance (absDistance crazyStud))
সাধারণভাবে floorDistance আর ceilingDistance ভিন্ন ফলাফল দেয়, কিন্তু একই নিশ্চয়তা দেয়।
এটাই ফ্যান্টম টাইপ কৌশলের শক্তি: ব্যবহারকারীদের শক্তিশালী নিশ্চয়তা বজায় রেখেই নমনীয় বিকল্প দেওয়া।
আপনি, মন্দের অধিপতি, সারা বিশ্বজুড়ে আপনার মন্দ ডাঞ্জনগুলোতে থাকা TreasureChest-গুলোর গুণমান নিয়ে দারুণ গর্ব করেন।
এই গুণমান ধরে রাখা সহজ নয়, আপনার ডাঞ্জনের ম্যানেজাররা বারবার ধন-সম্পদ বানানোয় গোলমাল করে ফেলে, তাই আপনি ঠিক করলেন তাদের আপনার ইচ্ছার বশে আনতে Elm API বানিয়ে দেবেন।
দুটি শর্তে আপনি নড়বেন না:
TreasureChest-কে অন্তত ৮ অক্ষরের একটি নিরাপদ পাসওয়ার্ড দিয়ে সুরক্ষিত থাকতে হবেTreasureChest-এ একটি অনন্য ধন-সম্পদ থাকতে হবেআপনার ডাঞ্জনের ম্যানেজাররা পাসওয়ার্ড ও ধন-সম্পদের সাজেশনের একটি অ্যারে দেবে, আর শুধু উপযুক্ত TreasureChest-গুলোই তৈরি হবে।
এই শর্তগুলো মেনে সাজেশনের একটি অ্যারে থেকে নিরাপদ চেস্ট তৈরি করার দুটি সম্ভাব্য উপায় আছে:
এই দুটি হয়তো একই ফলাফল নাও দিতে পারে (যেমন [("strong_password", GoldStatue), ("1234", GoldStatue)]-এর ক্ষেত্রে), কিন্তু আপনার তাতে আপত্তি নেই, তাই আপনি ডাঞ্জনের ম্যানেজারদেরই সিদ্ধান্ত নিতে দিতে চান।
ব্যবহারকারীদের কিছু পছন্দের স্বাধীনতা দেয়, তবুও চূড়ান্ত ফলাফলের বৈশিষ্ট্যগুলো নিশ্চিত করে, এমন একটি API? এটা তো ফ্যান্টম টাইপ কৌশলের জন্য একেবারে মানানসই!
TreasureChest টাইপ এবং তার সঙ্গী getTreasure আগেই দেওয়া আছে, কিন্তু চেস্ট সাজেশনের জন্য আপনাকে একটি টাইপ বানাতে হবে।
Chest টাইপ ইমপ্লিমেন্ট করুন, makeChest ইমপ্লিমেন্ট করুন এবং secureChest ও uniqueTreasures-এর টাইপ সিগনেচার ঠিক করুন।
makeChest ও makeTreasureChest-এর টাইপ সিগনেচার প্রয়োজনীয়তার অংশ হিসেবে আগেই দেওয়া আছে, সেগুলো বদলাবেন না।
একটি Chest-এ TreasureChest-এর মতোই ডেটা থাকবে এবং দুটি টাইপ আর্গুমেন্ট থাকবে, একটি treasure-এর জন্য আর conditions-এর জন্য একটি রেকর্ড ফ্যান্টম টাইপ।
মনে রাখবেন, Chest যেহেতু একটি ফ্যান্টম টাইপ ব্যবহার করে, তাই TreasureFactory মডিউলের বাইরে কোথাও যাতে এটি ব্যবহার করা না যায় সেজন্য এটিকে অস্বচ্ছ রাখা উচিত।
এখানে এটিকে আদৌ এক্সপোজ করা হয়নি।
ফ্যান্টম টাইপ হিসেবে এক্সটেনসিবল রেকর্ড ব্যবহার করে কনস্ট্রেইন্ট যোগ করতে secureChest ও uniqueTreasures-এর টাইপ সিগনেচার সম্পাদনা করুন।
secureChest নির্দিষ্ট শর্ত ছাড়া একটি Chest নেবে এবং তার ফ্যান্টম রেকর্ডে অতিরিক্ত securePassword : () শর্তসহ একটি Maybe Chest রিটার্ন করবে।
uniqueTreasures নির্দিষ্ট শর্ত ছাড়া একটি List Chest নেবে এবং ফ্যান্টম রেকর্ডে uniqueTreasure : () শর্তটি যোগ করে একটি List Chest রিটার্ন করবে।
Elm টেস্ট এক্সপোজ করা ফাংশন পায়, কিন্তু টাইপ সিগনেচার পায় না, তাই আপনি সঠিক সিগনেচার ব্যবহার করছেন কি না তা টেস্টের পক্ষে যাচাই করা অসম্ভব। অবশ্যই, আপনার সিগনেচার ঠিক আছে তার সবচেয়ে শক্তিশালী প্রমাণ হলো মডিউল ও টেস্ট কম্পাইল ও রান করাতে পারা, তবে আমরা একটি analyzer rule বানিয়েছি, যা আপনি আপনার সলিউশন সাবমিট করার পর আপনার টাইপ সিগনেচার যাচাই করবে।
চেস্ট প্রস্তুত হয়ে গেলে নিরাপদগুলো বেছে নিতে হবে।
secureChest ইমপ্লিমেন্ট করুন, যা ৮ বা তার বেশি অক্ষরের পাসওয়ার্ডযুক্ত চেস্টের জন্য শুধু Just ভ্যারিয়েন্ট রিটার্ন করবে।
যে Chest-গুলো এই শর্ত পূরণ করে, তাদের ফ্যান্টম টাইপে অতিরিক্ত securePassword : () শর্ত যোগ করা উচিত।
ডাঞ্জনে শুধু সবচেয়ে দুর্লভ ধন-সম্পদই থাকা উচিত; একই ধন-সম্পদের একটি কপি থাকলেও ধন-সম্পদটি সস্তা দেখায়।
uniqueTreasures ইমপ্লিমেন্ট করুন, যা Chest-এর একটি অ্যারে নেয় এবং ইনপুট অ্যারে যে Chest-গুলোর ধন-সম্পদ অনন্য সেগুলোর একটি অ্যারে রিটার্ন করে।
ইনপুটে কোনো ধন-সম্পদ দুবার এলে তা আউটপুটে থাকবে না।
ইনপুটের Chest-গুলোর নির্দিষ্ট শর্ত থাকবে না, আর আউটপুটেরগুলোতে অতিরিক্ত uniqueTreasure : () শর্ত যোগ করা থাকবে।
TreasureChest-গুলোর সেরাটাই উপভোগ করুন, যা দুঃসাহসীদের মধুতে মাছির মতো টেনে আনবে।
makeTreasureChest ইমপ্লিমেন্ট করুন, যা এমন একটি Chest নেয় যেটি একইসাথে নিরাপদ ও অনন্য, এবং একটি TreasureChest তৈরি করে।
TreasureChest যেহেতু একটি অস্বচ্ছ টাইপ, তাই এটিই একে তৈরি করার একমাত্র উপায় হবে; বাজে ডাঞ্জন ম্যানেজাররাও এটি নষ্ট করতে পারবে না।
Exercism-এ সাইন আপ করুন, Elm ট্র্যাকের 28টি কনসেপ্ট110টি অনুশীলনী আর সত্যিকারের মানুষের মেন্টরিং দিয়ে শিখুন ও দক্ষ হয়ে উঠুন, সম্পূর্ণ বিনামূল্যে।