HTML

Survive Developement

Itt olvashatod a Survive! nevű játék fejlesztésének állapotát, lépéseit. És mi is lesz a játék? Egy zombis-túlélős játék, ahol elsősorban a csapatmunkára építkezve kell megpróbálni életben maradni egy kihalt városban. A terep teljesen a tietek, nincsenek szabályok: éljetek túl, ahogy tudtok!


Küldj e-mailt nekünk:
gilgamesco@gmail.com

Sikolyok

Ettől tépjük a hajunkat:

Friss topikok

  • Sir Butcher: A gyors mozgású ütközés-érzékelés majd a lövésnél lesz topic :D A második esetben teljesen igaza... (2012.04.05. 16:38) Ütközésérzékelés
  • _fpeti_: Halad ez. (2012.04.04. 22:01) Gravitáció
  • Sir Butcher: Az sem rossz, az tény :D Szerencsére egyelőre annyi különbözőt kell csinálnom, hogy esélyem sincs ... (2012.02.20. 21:48) Scenery - Még több látvány
  • Sir Butcher: Na, ideírom: obj-nél megoldottam a csontokat. Melléktermékként összejöttek, extra számítás nélkül ... (2011.12.02. 11:48) Model Animálás - a probléma, és a (vélt) megoldás
  • Burwor: "A tesztvárosban sétálgatva belefutsz egy házba, aminek hiányzik egy fala. Mit csinálsz?" Zárva a... (2011.11.10. 15:00) Sziduri - a grafmotor bemutatkozik

A state monád

2011.09.13. 10:17 :: GizmoSDK

Hát, tutorialt nem írok, de ha valakit bővebben is érdekel, hogy mi miatt téptem a hajamat mostanában, annak megpróbálok valami magyarázatfélét összedobni. Ugye a Haskell egy tiszta funkcionális nyelv, ami azt jelenti, hogy minden függvény egyedül a bemeneti paraméterektől függ, és mástól nem. Tehát nem függ semmiféle állapottól, globális változóktól, ilyesmiktől. Persze, minden program, ami megírható C-ben, az megírható Haskellben is, de ha ennél a módszernél maradnánk, a Haskellerek kihalnának koffein-túladagolásban (én mondjuk előbb keresnék más nyelvet, mint hogy rászokjak a kávéra, de ez most részletkérdés :)).

Hát akkor ilyenkor mit lehet tenni?

Ugye, meg lehet azt csinálni, hogy fogjuk a függvényeket, és ha mégis akarjuk szimulálni, hogy valami állapoton dolgoznak, akkor egyszerűen hozzácsapjuk a bemeneti paraméterekhez a megváltoztatandó állapotot, és a kiköpött eredményhez hozzáragasztjuk a megváltozott állapotot. Most nincs kedvem példát írni, de higgyétek el, rohadt fárasztó munka lenne. Szerencsére kitalálták a monádokat (ha valaki nomádnak olvassa, isten bizony, szétütöm :D), amikkel ez sokkal egyszerűbbé válik.

Elöljáróban leszögezném, hogy a monádok nem az egyetlen eszközei az ilyen jellegű problémák megoldásának. valamint a Haskellnek sincs szüksége monádokra. Egyszerűen ezt a megoldást választották a készítők. De ha valaki talál jobbat, ami neki tetszik, hát akkor használja (vagy írja meg) azt. Mindenesetre a monádok a legszélesebb körben elfogadottak, ezért én is azokat használom.

Tehát, mi a monád? Ha el akarom ijeszteni az olvasókat a funkcionális programozástól, akkor annyit kell csak mondanom, hogy "a monádok tulajdonképpen monoidok az endofunktorok kategóriájában, mit olyan nehéz ezen felfogni?" Csak épp az a helyzet, hogy fogalmam sincs, hogy mit akar jelenteni ez a mondat XD (Oké, a második tagmondatot még értem :D) Szóval, igazából nem fontos tudni, mik is azok a monádok. Tényleg. Én sem tudom, mégis használom. (A mágnesességet se tudjuk, hogy micsoda, csak elméleteink vannak róla, de mégis gond nélkül használjuk :)) (Egyébként SirButcher rávilágított arra, hogy tulajdonképpen a monád olyasmi, mint egy osztály az imperatív nyelvekben. Azt hiszem, ez a legjobb megközelítés.)

Többfajta monád létezik, és gyakorlatilag bármikor bárki írhat magának egyet minden további nélkül. Igazából arra jó, hogy bizonyos speciális eljárások közös elemét egy magasabb absztrakciós szintre emeljük, egyszerűsítve ezáltal a kódot. Pl. a State monád a mi esetünkben az állapottal manipuláló függvények írását egyszerűsíti le jelentősen. De van pl az IO monád, ami a mellékhatásos függvények írását segíti, van a Error monád, ami a hibakezelésben hasznos, és így tovább.

Az alapötlet az, hogy fogjuk az állapotot, és becsomagoljuk egy monádba. Ami ugye azért jó, mert így elrejtjük magunk elől a komplexitást, és nem fogunk elájulni, amikor meglátjuk a saját kódunkat :D Ezután az így létrejött monádot kombinálhatjuk egymással, és a végén kiszedhetjük belőle az eredményt.

A State monád tulajdonképpen egy olyan függvény, ami egy állapotból egy másikba képez. Kellő mennyiségű state monád alkalmazásával egy kezdeti állapotból eljuthatunk egy kívánt végállapotba. Teljesen olyan, mintha imperatív nyelvekben kódolnánk. És mégis, a színfalak mögött csak ugyanolyan függvények vannak, mint eddig, amik csakis a bementi paraméterektől függnek. A smink felrakása után viszont teljesen úgy néznek ki, mintha állapottól függő imperatív eljárások lennének.

Példa.

Legyen egy bérgyilkosunk, akinek van egy listája a likvidálandó illetőkről. Ehhez hozzunk létre egy típust, ami mondjuk legyen a Target. Ez tartalmazza az illető fontosabb információit, pl nevet, születési évet, képeket, mikor zuhanyzik, mikor megy wc-re, stb. A bérgyilkosunk néha kap új megbízásokat, és néha elvégez feladatokat. Tehát kell két függvény, az egyik megkap egy megbízási listát, egy új megbízást, és visszaad egy olyan listát, amit úgy kapunk, hogy a listához hozzárakjuk az új megbízást.

addTarget :: [Target] -> Target -> [Target]
addTarget targetList newTarget = Hozzáadja a targetListhez a newTarget-ot.

Most csináljunk egy olyat, ami egy adott illetőt eltávolít a listából, mert jól megöltük. Feltételezzük, hogy csak olyan embert ölünk meg, aki a listában van:

removeTarget :: [Target] -> Target -> [Target]
removeTarget targetList killedTarget = Eltávolítja a targetListből a killedTargetot.

Tegyük fel, hogy érkezik kettő megbízás, amiből megöltünk egyet, de jött még egy. Írjuk fel, hogy nézne ki az első megbízás:

initTargetList = []
test = addtarget initTargetList (Target "nulloid" ... )

vagy infix jelöléssel:

test = initTargetList `addTarget` (Target "nulloid" ... )

Vagyis az initTargetList-hez `addjuk hozzá` a "nulloid" nevű célszemélyt. (A Target "..." ... egy típuskonstruktor. Létrehoz egy Target típust a megadott adatokból. A `függvény` jelölés pedig a függvények infix alkalmazását teszi lehetővé (pl a plus függvény, ami összead két számot, írható úgy is, hogy plus 5 3, de írható így is: 5 `plus` 3. Igaz, a legtöbben a + operátort használják erre a célra :D))

A ... helyére kerülne az illető többi infója, de a személyiségi jogokra hivatkozva nem adhatok ki többet. Na de mi van, ha új megbízást kapunk? Hát, akkor ennek a addTarget függvénynek kell venni az eredményét, és arra megint meghívni egy addTargetot. A matematikában ezt ragyogóan elérik az f(g(x)) jelöléssel, vagy máshogy: (g ° f)(x). Mi a ° helyett a pontot haszáljuk:

test = (initTargetList `addTarget` (Target "nulloid" ...)) . (`addtarget` (Target "5.t.3.v.3.n" ...))

De ez még mindig túl sok munka begépelni. Úgyhogy használjuk a state monádot:

addTarget :: Target -> State [Target] [Target]
addTarget target = egy adott listához hozzáadjuk a target-t.

Fontos, hogy ez egy részlegesen alkalmazott függvény. Olyan ez, mintha a (+) függvényt csak egy számra használnánk:

plus3 = (+) 3(azért írjuk zárójelbe, mert az operátorok tulajdonképpen függvények, csak rájuk egy kicsit más szintaxis vonatkozik)

És akkor írhatunk ilyet (a -- jeltől kezdve a sor végéig komment van):

a = plus3 4    -- ebből lesz a 7
b = plus3 9    -- ebből 12
c = plus3 (-2) -- ebből meg 1

Ez még önmagában értelmetlen. Azonban írjunk egy olyat, hogy

plus x = (+) x

És akkor jöhet ez:

a = (plus 3) 6
b = (plus 42) 0

vagyis a (plus 3) egy függvény, amit aztán alkalmazunk a 6-ra. Így jön ki a 48.

Mire is jó ez? Hát gondoljunk csak bele... Minek írjuk meg az összes lehetséges értékre a listához hozzáadást (ami kb 6,7 milliárd esetet jelentene a Föld jelen lakosságát tekintve), amikor megírhatjuk részlegesen, és onnantól fogva alkalmazhatjuk mindenkire?

No, a removetargetot hasonlóan meg lehet írni, de már így is túl hosszú a bejegyzés, úgyhogy térjünk a lényegre. Miután a State monádban definiálva van a monádok összekapcsolása (>>=), így sokkal könnyebben meg lehet írni az előző kódot így:

test = addTarget (Target "nulloid" ...) >>= (\state ->
         addTarget (Target "5.t.3.v.3.n" ...))

De ez még mindig rövidíthető, ugyanis az anonim függvények (lambdák, esetünkben a \state kezdetű rész) kiszedhető egy speciális jelöléssel. A Haskell az ilyen monadikus kódokra találta ki a do-jelölést, ami így néz ki:

test = do
    addtarget (Target "nulloid" ...)
    addTarget (Target "5.t.3.v.3.n" ...)

És akkor jöjjön a bérgyilkosunk tegnapi napjának teljes leírása:

yesterday = do
    addtarget    (Target "nulloid"     ...)
    addTarget    (Target "5.t.3.v.3.n" ...)
    removeTarget (Target "nulloid"     ...)
    addTarget    (Target "SirButcher"  ...)
    removeTarget (Target "5.t.3.v.3.n" ...)
    removeTarget (Target "SirButcher"  ...)

És jól ki is nyírtuk a Survive! fejlesztőit, bérgyilkosunk elégedetten örül. Bele sem merek gondolni, hogy nem monadikus formában hány oldal lenne.

Szólj hozzá!

Címkék: state haskell monád

A bejegyzés trackback címe:

https://survivedev.blog.hu/api/trackback/id/tr193222636

Kommentek:

A hozzászólások a vonatkozó jogszabályok  értelmében felhasználói tartalomnak minősülnek, értük a szolgáltatás technikai  üzemeltetője semmilyen felelősséget nem vállal, azokat nem ellenőrzi. Kifogás esetén forduljon a blog szerkesztőjéhez. Részletek a  Felhasználási feltételekben és az adatvédelmi tájékoztatóban.

Nincsenek hozzászólások.
süti beállítások módosítása