Kurzusok
/
Elm
Elm
/
Feladatok
/
Kincsgyár
Kincsgyár

Kincsgyár

Tanulófeladat

Bevezetés

Fantomtípusok

Az Elm típusai lehetővé teszik a generikus típusokat, amelyek általában rugalmasabbá teszik a felületet. Például a Maybe a típus bármilyen típusú értéket tárolhat.

type Maybe a = Nothing | Just a

Vegyük észre, hogy a fenti példában az a típusparaméter az egyenlőségjel (=) mindkét oldalán szerepel. Azt mondjuk, hogy az a a típusdefiníción belül adathoz van kötve.

A fantomtípus-technika

Bizonyos esetekben azonban a típusparaméter csak az egyenlet bal oldalán jelenik meg.

type Distance unit = Distance Float

A fenti Distance definíciójában a unit szabad típusparaméter, amely nincs a típuson belüli adathoz kötve. Ezt a típust fantomtípusnak is nevezhetjük.

Meglepően hasznos, amikor fordítási időben szeretnénk megkötéseket érvényesíteni. Tegyük fel például, hogy csak azonos egységű távolságokat akarunk összeadni.

-- 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)

A modulon kívüli kód nem fér hozzá a Distance típus belsejéhez. A felhasználók összeadhatnak két Distance Meter értéket, de egy Distance Meter és egy Distance Foot értéket nem.

import Distance exposing (Distance)

-- Compiles
twoMeters = Distance.add Distance.meter Distance.meter

-- Does not compile
errDist = Distance.add Distance.meter Distance.foot

Bővíthető rekordok mint fantomtípusok

Nincs semmilyen megkötés arra nézve, hogy mely típusok lehetnek fantomok, és rekordtípusok is használhatók. Fantomtípusként használva a rekordok olyan összetett megkötéseket tudnak kifejezni, amelyek függvényeken keresztül átalakíthatók.

Vegyünk fel egy új távolságegységet a Distance modulon belül, LegoBlock néven. A fizikai építőkockák, mivel szabályozott tárgyak, mindig két tulajdonsággal bírnak: a távolságuk nem tört és nem negatív.

type LegoBlock = LegoBlock

fourStuds : Distance { properties | unit: LegoBlock, nonFractional : (), nonNegative: () }
fourStuds = Distance 4.0

A nonFractional és a nonNegative egy olyan rekord mezői, amely csak típusargumentumban létezik, ezért kézenfekvő a () típust adni nekik, amelyet egységtípusnak hívunk, és amely nem hordoz több információt annál, mint hogy ott van.

Természetesen tetszőleges LegoBlock távolságot is kaphatunk, például távolságkülönbségek vagy arányok kiszámítása után.

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

A fenti értékek mind érvényesek és lefordulnak, viszont különösen érdekelnek minket az olyan értékek, mint a fourStuds, amelyek egyszerre rendelkeznek a nonFractional és a nonNegative tulajdonsággal, mert olyan fizikai építőkockákat képviselnek, amelyek összekapcsolhatók a következővel:

combineLegoBlocks
  :  Distance { properties | unit: LegoBlock, nonFractional : (), nonNegative: () }
  -> Distance { properties | unit: LegoBlock, nonFractional : (), nonNegative: () }
  -> Distance { properties | unit: LegoBlock, nonFractional : (), nonNegative: () }
combineLegoBlocks = add

Adjunk hozzá néhány függvényt, amelyekkel a felhasználók létrehozhatnak és finomíthatnak LegoBlock távolságokat.

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)

Vegyük észre, hogy a floorDistance, a ceilingDistance és az absDistance a LegoBlock-tól eltérő egységgel is megbirkózik, és általában semmilyen feltevéssel nem él a bemeneti tulajdonságokkal kapcsolatban, csupán garantálják, hogy a kimenet rendelkezik egy adott tulajdonsággal, a nonFractional vagy a nonNegative tulajdonsággal.

Nézzünk meg néhány eredményt.

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))

Általában a floorDistance és a ceilingDistance más eredményt ad, de ugyanazokat a garanciákat nyújtja. Ez a fantomtípus-technika ereje: rugalmas választási lehetőségeket kínál a felhasználóknak, miközben erős garanciákat tart fenn.

Utasítások

Te, a Gonosz Ura, nagyon büszke vagy a világ minden táján lévő gonosz kazamataidban található TreasureChest-ek minőségére. Nem könnyű tartani a színvonalat: a kazamatafelügyelőid folyton elrontják a kincskészítést, ezért úgy döntesz, hogy adsz nekik egy Elm API-t, amellyel az akaratodnak vetheted alá őket.

Két feltétel van, amiben nem engedsz:

  1. a TreasureChest-et legalább 8 karakter hosszú, biztonságos jelszó védje
  2. egy adott kazamatában minden TreasureChest egyedi kincset őrizzen

A kazamatafelügyelőid összeállítanak egy listát jelszó- és kincsjavaslatokból, és csak a megfelelő TreasureChest-ek fognak létrejönni.

Ezekkel a feltételekkel kétféleképpen hozhatók létre biztonságos ládák egy javaslatlistából:

  1. eltávolítod azokat, amelyek jelszava nem biztonságos, majd azokat, amelyeknek ismétlődő kincsük van
  2. először azokat távolítod el, amelyeknek ismétlődő kincsük van, majd azokat, amelyek jelszava nem biztonságos

Lehet, hogy ez a kettő nem ugyanarra az eredményre vezet (például a [("strong_password", GoldStatue), ("1234", GoldStatue)] esetében), de neked mindegy, melyik, úgyhogy a kazamatafelügyelőkre bíznád a döntést.

Egy API, amely némi választási szabadságot hagy a felhasználóknak, mégis garantálja a végeredmény bizonyos tulajdonságait? Ez pontosan a fantomtípus-technika esete!

1. Adj helyőrzőt a kincsesládákhoz

A TreasureChest típus és a hozzá tartozó getTreasure már adott, neked viszont ki kell találnod egy típust a ládajavaslathoz.

Valósítsd meg a Chest típust, valósítsd meg a makeChest-et, és javítsd ki a secureChest és a uniqueTreasures típusaláírását. A makeChest és a makeTreasureChest típusaláírása már adott a követelmények részeként, ezeket ne módosítsd.

A Chest ugyanazokat az adatokat tárolja, mint a TreasureChest, és két típusargumentuma legyen: az egyik a treasure, a másik pedig egy rekord fantomtípus a conditions számára. Vedd figyelembe, hogy mivel a Chest fantomtípust használ, ezért átlátszatlannak kell lennie, hogy megakadályozzuk a használatát a TreasureFactory modulon kívül. Ebben az esetben ugyanis egyáltalán nincs is exportálva.

Módosítsd a secureChest és a uniqueTreasures típusaláírását úgy, hogy bővíthető rekordokkal, fantomtípusként add hozzá a megkötéseket. A secureChest egy olyan Chest-et kapjon, amelynek nincsenek konkrét feltételei, és egy Maybe Chest-et adjon vissza, amelynek fantomrekordjában ott van a securePassword : () extra feltétel. A uniqueTreasures egy olyan List Chest-et kapjon, amelynek nincsenek konkrét feltételei, és egy olyan List Chest-et adjon vissza, amelynek fantomrekordjához hozzáadtuk a uniqueTreasure : () extra feltételt.

Note

Az Elm tesztek hozzáférnek az exportált függvényekhez, a típusaláírásokhoz viszont nem, ezért a tesztek nem tudják ellenőrizni, hogy a megfelelő aláírásokat használod-e. Persze a legjobb jele annak, hogy jó aláírásaid vannak, az, ha sikerül lefordítani és futtatni a modult és a teszteket, de készítettünk egy elemzőszabályt, amely a megoldásod beküldése után ellenőrzi a típusaláírásaidat.

2. Válogasd ki a biztonságos ládákat

Ha készen vannak a ládák, ki kell válogatnod a biztonságosakat.

Valósítsd meg a secureChest-et, amely csak azokra a ládákra ad vissza Just változatot, amelyek jelszava legalább 8 karakter hosszú. Azok a Chest-ek, amelyek teljesítik ezt a feltételt, kapjanak egy extra securePassword : () feltételt a fantomtípusukban.

3. Válogasd ki az egyedi kincseket

Egy kazamatába csak a legritkább kincsek kerülhetnek: már egyetlen másolat is olcsóvá teszi a kincset.

Valósítsd meg a uniqueTreasures-t, amely egy Chest-ekből álló listát kap, és azoknak a Chest-eknek a listáját adja vissza, amelyeknek egyedi a kincse a bemeneti listában. Ha egy kincs kétszer szerepel a bemenetben, akkor nem kerülhet a kimenetbe. A bemeneti Chest-eknek nem szabad konkrét feltételekkel rendelkezniük, a kimenetieknek pedig hozzá kell adni a uniqueTreasure : () extra feltételt.

4. Élvezd a végeredményt

Élvezd a legjobb TreasureChest-eket, amelyek biztos, hogy úgy vonzzák a kalandorokat, mint a méz a legyeket.

Valósítsd meg a makeTreasureChest-et, amely egy olyan Chest-et kap, amely egyszerre biztonságos és egyedi, és létrehoz egy TreasureChest-et. Mivel a TreasureChest átlátszatlan típus, ez lesz az egyetlen módja létrehozni egyet, így még a gyatra kazamatafelügyelők sem tudják elrontani.

Szerkesztés GitHubon A hivatkozás új ablakban vagy lapon nyílik meg
Elm Exercism

Készen állsz elkezdeni a(z) Kincsgyár feladatot?

Iratkozz fel az Exercism-re, hogy megtanuld és elsajátítsd a(z) Elm nyelvet 28 fogalom110 feladat segítségével, valódi emberi mentorálással, mindez ingyen.