ट्रैक
/
Elm
Elm
/
अभ्यास
/
खज़ाने की फैक्ट्री
खज़ाने की फैक्ट्री

खज़ाने की फैक्ट्री

सीखने का अभ्यास

परिचय

फैंटम टाइप

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 दिया जाए।

दो शर्तें हैं जिन पर आप ज़रा भी नहीं झुकेंगे:

  1. हर TreasureChest की सुरक्षा कम से कम 8 अक्षरों वाले एक सुरक्षित पासवर्ड से होनी चाहिए
  2. किसी एक खास कालकोठरी में हर TreasureChest के पास एक अनोखा खज़ाना होना चाहिए

आपके कालकोठरी के प्रबंधक पासवर्ड और खज़ाने के सुझावों की एक सूची बनाएँगे, और उससे केवल उपयुक्त TreasureChest ही बनाए जाएँगे।

इन शर्तों के साथ, सुझावों की सूची से सुरक्षित चेस्ट बनाने के दो तरीके हो सकते हैं:

  1. जिनके पासवर्ड असुरक्षित हैं उन्हें हटा दीजिए, फिर जिनके खज़ाने दोहराए हुए हैं उन्हें हटा दीजिए
  2. जिनके खज़ाने दोहराए हुए हैं उन्हें हटा दीजिए, फिर जिनके पासवर्ड असुरक्षित हैं उन्हें हटा दीजिए

इन दोनों से एक जैसे परिणाम नहीं मिलेंगे (उदाहरण के लिए [("strong_password", GoldStatue), ("1234", GoldStatue)] के लिए), लेकिन आपको किसी भी तरह से कोई फर्क नहीं पड़ता, इसलिए आप चाहते हैं कि कालकोठरी के प्रबंधक खुद तय करें।

एक ऐसा API जो उपयोगकर्ताओं को चुनने की कुछ छूट देता है, लेकिन फिर भी अंतिम परिणाम के गुणों की गारंटी देता है? यह तो फैंटम टाइप तकनीक के लिए एकदम सही लगता है!

1. खज़ाने के चेस्ट के लिए प्लेसहोल्डर बनाइए

TreasureChest टाइप और उसका साथी getTreasure पहले से दिए हुए हैं, लेकिन आपको चेस्ट के सुझाव के लिए एक टाइप बनाना है।

Chest टाइप लागू कीजिए, makeChest लागू कीजिए और secureChest तथा uniqueTreasures के टाइप सिग्नेचर ठीक कीजिए। makeChest और makeTreasureChest के टाइप सिग्नेचर ज़रूरतों के हिस्से के तौर पर पहले से दिए गए हैं, इन्हें मत बदलिए।

एक Chest में वही डेटा होना चाहिए जो एक TreasureChest में होता है, और उसमें दो टाइप आर्गुमेंट होने चाहिए: एक treasure के लिए और एक conditions के लिए रिकॉर्ड फैंटम टाइप। ध्यान दीजिए कि चूँकि Chest एक फैंटम टाइप का उपयोग करता है, इसलिए इसे अपारदर्शी होना चाहिए, ताकि TreasureFactory मॉड्यूल के बाहर कहीं भी इसका उपयोग न हो सके। इस मामले में, इसे बाहर उपलब्ध ही नहीं कराया गया है।

secureChest और uniqueTreasures के टाइप सिग्नेचर में बदलाव करके उनमें एक्सटेंसिबल रिकॉर्ड को फैंटम टाइप के रूप में इस्तेमाल करते हुए कंस्ट्रेंट जोड़िए। secureChest को बिना किसी खास शर्त वाला एक Chest लेना चाहिए और एक Maybe Chest लौटाना चाहिए, जिसके फैंटम रिकॉर्ड में securePassword : () की अतिरिक्त शर्त हो। uniqueTreasures को बिना किसी खास शर्त वाले List Chest लेने चाहिए और List Chest लौटाने चाहिए, जिनके फैंटम रिकॉर्ड में uniqueTreasure : () की अतिरिक्त शर्त जोड़ी गई हो।

Note

Elm के टेस्ट को बाहर उपलब्ध कराए गए फंक्शन दिख जाते हैं, लेकिन टाइप सिग्नेचर नहीं, इसलिए टेस्ट के लिए यह जाँचना असंभव है कि आप सही सिग्नेचर इस्तेमाल कर रहे हैं या नहीं। बेशक, सही सिग्नेचर होने का सबसे पक्का सबूत यही है कि मॉड्यूल और टेस्ट कंपाइल होकर चल जाएँ, लेकिन हमने एक एनालाइज़र नियम बनाया है, जो आपके हल जमा करते ही आपके टाइप सिग्नेचर की जाँच कर लेगा।

2. सुरक्षित चेस्ट चुनिए

चेस्ट तैयार हो जाने के बाद आपको उनमें से सुरक्षित चेस्ट चुनने हैं।

secureChest लागू कीजिए, जो केवल उन चेस्ट के लिए Just वेरिएंट लौटाता है जिनका पासवर्ड 8 या उससे अधिक अक्षरों का है। जो Chest यह शर्त पूरी करते हैं, उनके फैंटम टाइप में securePassword : () की अतिरिक्त शर्त जोड़ी जानी चाहिए।

3. अनोखे खज़ाने चुनिए

कालकोठरी में केवल सबसे दुर्लभ खज़ानों को ही जगह मिलनी चाहिए, एक भी दूसरी प्रति खज़ाने को सस्ता दिखा देती है।

uniqueTreasures लागू कीजिए, जो Chest की एक सूची लेता है और उन Chest की सूची लौटाता है जिनका खज़ाना इनपुट सूची में अनोखा है। अगर कोई खज़ाना इनपुट में दो बार आता है, तो वह आउटपुट में नहीं होना चाहिए। इनपुट वाले Chest की कोई खास शर्त नहीं होनी चाहिए, और आउटपुट वाले Chest में uniqueTreasure : () की अतिरिक्त शर्त जोड़ी गई होनी चाहिए।

4. अंतिम उत्पाद का आनंद लीजिए

ऐसे बेहतरीन TreasureChest का आनंद लीजिए, जो साहसी लोगों को शहद पर मक्खियों की तरह खींच लाएँगे।

makeTreasureChest लागू कीजिए, जो ऐसा Chest लेता है जो सुरक्षित भी है और अनोखा भी, और उससे एक TreasureChest बनाता है। चूँकि TreasureChest एक अपारदर्शी टाइप है, इसे बनाने का यही एकमात्र तरीका होगा; घटिया कालकोठरी प्रबंधक भी इसे बिगाड़ नहीं पाएँगे।

GitHub के ज़रिए संपादित करें यह लिंक एक नई विंडो या टैब में खुलता है
Elm Exercism

खज़ाने की फैक्ट्री शुरू करने के लिए तैयार हैं?

Exercism पर साइन अप कीजिए और Elm को 28 कॉन्सेप्ट110 अभ्यास तथा असली इंसानों से मिलने वाली मेंटरिंग के साथ सीखिए और उसमें महारत हासिल कीजिए, वह भी बिल्कुल मुफ्त।