Egy tiszta funkcionális nyelven, mint az Elm, egy ugyanazokkal az argumentumokkal meghívott függvény mindig ugyanazt az értéket adja vissza.
Ezért egy rand : Int típusszignatúrájú függvényt csak rand = 4 alakban lehet megvalósítani, ami nem sok jót ígér véletlen egészek előállításához.
Szóval hogyan állítsunk elő véletlen értékeket Elmben?
Két részre bontjuk a problémát: először leírjuk, milyen értéket szeretnénk előállítani egy Random.Generator a segítségével, aztán előállítunk egy értéket.
Az első módja az érték előállításának, hogy létrehozunk egy Random.Seed értéket az initialSeed : Int -> Seed segítségével, majd használjuk a step : Generator a -> Seed -> ( a, Seed ) függvényt, amely visszaad egy értéket és egy új magot.
Vegyük észre, hogy mindkét függvény tiszta, tehát ha kétszer hívjuk meg őket ugyanazokkal az argumentumokkal, ugyanazokat az értékeket kapjuk.
A második mód az érték előállítására a generate : (a -> msg) -> Generator a -> Cmd msg használata, de erre csak egy Elm-alkalmazáson belül van lehetőség.
Ilyenkor az Elm futtatókörnyezete a step mellett külső, nem tiszta erőforrásokat is igénybe vehet a magok előállításához.
Mostantól a generátorokra összpontosítunk.
A példák kedvéért tegyük fel, hogy az initialSeed és a step segítségével definiáltunk egy generate : Int -> Generator a -> List a függvényt, amely adott számú értéket állít elő egy generátorból.
A Random modul két alapvető generátort kínál egészek és tizedes törtek adott tartományon belüli előállítására:
generate 5 (Random.int -5 5)
--> [0, 3, -5, 5, 0]
generate 3 (Random.float 0 5)
--> [1.61803, 3.14159, 2.71828]
Ezeket az értékeket össze lehet fűzni tuple-okká vagy értékek listáivá:
generate 2 (Random.list 3 (Random.int 0 3))
--> [[0, 3, 3], [1, 3, 2]]
generate 2 (Random.pair (Random.int 0 3) (Random.float 10 10.3))
--> [(0, 10.23412), (2, 10.17094)]
Az elm-community/random-extra csomag sokkal több generátort kínál különféle adatszerkezetekhez: stringekhez, dátumokhoz, szótárakhoz, tömbökhöz, halmazokhoz stb.
A Random.constant segítségével olyan generátorokat hozhatunk létre, amelyek mindig csak egyetlen értéket adnak vissza:
generate 4 (Random.constant "hello")
--> ["hello", "hello", "hello", "hello"]
A Random.uniform segítségével véletlenszerűen választhatunk a megadott elemek közül, egyenlő valószínűséggel:
generate 5 (Random.uniform Red [Green, Blue])
--> [Red, Blue, Blue, Green, Red]
A Random.uniform két argumentumot vár (Red és [Green, Blue]), így garantált, hogy legalább egy érték közül lehet választani, hiszen egy sima lista üres is lehet.
A valószínűségeket a Random.weighted segítségével is hangolhatjuk:
generate 5 (Random.weighted (80, Red) [(15, Green), (5, Blue)])
--> [Red, Red, Green, Red, Red]
Az értékeknek nem kell 100-at kiadniuk, úgyis újranormalizálódnak.
A Random.map segítségével elérhetjük egy generátor belsejét:
generate 3 (Random.int 1 6 |> Random.map (\n -> n * 10))
--> [30, 60, 10]
A Random.map2-től egészen a Random.map5-ig is eljuthatunk, hogy több generátort kombináljunk:
position =
Random.map3
(\x y z -> Position x y z)
(Random.float -100 100)
(Random.float -100 100)
(Random.float -100 100)
generate 1 position
--> [Position 33.788 -98.321 10.0845]
Bonyolultabb felhasználásokhoz itt van a Random.andThen : (a -> Generator b) -> Generator a -> Generator b, amellyel az egyik generátor által előállított értéket felhasználhatjuk egy másik létrehozásához:
bool = Random.uniform True [False]
failHalfOfTheTime : Generator a -> Generator (Maybe a)
failHalfOfTheTime generator =
bool
|> Random.andThen
(\boolResult ->
if boolResult then
Random.map Just generator
else
Random.constant Nothing
)
generate 6 (Random.int 0 1 |> failHalfOfTheTime)
--> [Nothing, Just 1, Just 0, Nothing, Just 1, Nothing]
Néha hasznos, ha egy generátort önmagára rekurzívan definiálunk.
Ilyenkor szükség lehet a Random.lazy használatára, hogy megakadályozzuk a fordítót abban, hogy végtelenül kifejtse a generátort.
type Peano
= Zero
| Next Peano
peano : Generator Peano
peano =
Random.uniform (Random.constant Zero)
[ Random.map Next (Random.lazy (\_ -> peano))
]
|> Random.andThen identity
generate 12 peano
--> [Zero, Next(Next(Zero)), Zero, Next(Zero), Zero, Zero, Next(Zero), Zero, Zero, Next(Zero), Next(Next(Next(Zero)))]
Ez a példa kicsit nehéz, úgyhogy bontsuk szét.
A Peano egy olyan típus, amely a pozitív egészeket ábrázolja: a Zero a 0, a Next(Zero) az 1, a Next(Next(Zero)) a 2, és így tovább.
A peano definíciója véletlen Peano számot ad nekünk.
Először is jegyezzük meg, hogy a Random.lazy nem tesz hozzá semmit a logikához, csupán megakadályozza, hogy a fordító vég nélkül peano-t írjon a peano-ba.
A Random.uniform segítségével választunk a nulla (50% valószínűséggel) és egy másik Peano szám plusz egy (50% valószínűséggel) között.
Az előző példával ellentétben azonban a Random.uniform ezúttal nem értékek közül választ (mint a Zero), hanem generátorok közül (mint a Random.constant Zero), mert magát a peano-t szeretnénk használni.
Ez azt jelenti, hogy a Random.uniform Generator (Generator Peano) típusú értéket ad vissza, ami nem az, amire szükségünk van.
A generátor „kilapításához” beleküldjük a Random.andThen : (a -> Generator b) -> Generator a -> Generator b függvénybe, ahol a b a Peano legyen.
Mivel a Generator a valójában Generator (Generator Peano), az a csak Generator Peano lehet, a (a -> Generator b) függvénynek pedig (Generator Peano -> Generator Peano) típusúnak kell lennie.
Semmit sem szeretnénk módosítani, így az identity a helyes választás.
Végül, milyen számokat fog a peano előállítani?
Tudjuk, hogy az esetek 50%-ában 0-t ad, az esetek 50%-ában pedig önmaga egy további iterációját plusz egyet.
Ez azt jelenti, hogy a számok 0 (az esetek 50%-ában), 1 (25%-ában), 2 (12%-ában), 3 (6%-ában) stb. lesznek.
A peano tetszőlegesen nagy számokat állíthat elő, de exponenciálisan csökkenő valószínűséggel.
Labirintuskutató vagy, azon dolgozol, hogy kazamaták és kalandterep egy új generációját tervezd meg.
Elméleti szakember vagy, ami azt jelenti, hogy nem foglalkozol a labirintusok fizikai elrendezésével (az a labirintusmérnökök feladata), hanem a matematikai bonyolultságuk érdekel: elágazási tényező, maximális mélység és hasonlók.
Úgy döntesz, hogy a következő projektedhez véletlengenerálási megközelítéssel látsz hozzá.
A jó labirintusok nagy része zsákutcákból áll.
Definiáld a deadend generátort úgy, hogy mindig a DeadEnd változatot generálja.
Adjunk a kalandoroknak okot arra, hogy bemerészkedjenek a labirintusaidba.
Definiáld a treasure generátort úgy, hogy egyenlő valószínűséggel Gold, Diamond vagy Friendship értéket generáljon.
Ezután definiáld a room generátort úgy, hogy a treasure által generált értékeket a Room változatba csomagolja.
És most jöjjön a labirintus szíve és lelke: az elágazások.
A branch generátor egy Generator Maze argumentumot kap, ami egy tetszőleges labirintus generálására képes generátor.
Definiáld a branch generátort úgy, hogy egy Branch változatot generáljon, amely több elágazást tartalmaz, és ezek az argumentuma által generált labirintusokhoz vezetnek.
Az elágazások száma 2 és 4 között legyen (ennél többtől már dühös lenne a labirintusmérnök), egyenlő valószínűséggel, véletlenszerűen meghatározva.
Ideje munkához látni.
Definiáld a maze generátort úgy, hogy az esetek 60%-ában zsákutcát, 15%-ában kincses szobát, 25%-ában pedig elágazást generáljon.
Az elágazások rekurzívan, a maze segítségével generáljanak új labirintusokat.
Ügyelj rá, hogy a generátorban a deadend, a room és a branch is szerepeljen.
Egy elágazás átlagosan 3 labirintust tartalmaz, de ezeknek csak 25%-a ágazik el újra, ami kevesebb mint egy, így a generált labirintusaid valószínűleg előbb-utóbb véget érnek.
A maze generátorod szép, de van néhány problémája:
MazeMakerSupport.mazeDepth számítja ki)Definiáld a mazeOfDepth n generátort úgy, hogy egy n mélységű labirintust generáljon.
Csak a legmélyebb, 0 szint tartalmazhat zsákutcát vagy kincses szobát (egyenlő valószínűséggel), minden más szint egy elágazás legyen, amely rekurzívan, a mazeOfDepth segítségével generált n-1 mélységű labirintusokat tartalmaz.
Ügyelj rá, hogy a generátorban a deadend, a room és a branch is szerepeljen.
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.