Folosind monad de stat pentru a ascunde starea explicită

Încerc să scriu un mic joc în Haskell și există o cantitate destulă de stat necesară pentru a trece. Vreau să încerc să ascund statul cu statul monad

Acum m-am confruntat cu o problemă: funcții care iau statul și un argument au fost ușor de scris pentru a lucra în monad de stat. Dar există și funcții care iau doar statul ca argument (și returnează o stare modificată sau, eventual, altceva).

Într-o parte a codului meu, am această linie:

let player = getCurrentPlayer state

Aș dori să nu ia stat, și în loc să scrie

player <- getCurrentPlayerM

în prezent, implementarea sa arată astfel

getCurrentPlayer gameState = 
  (players gameState) ! (on_turn gameState)

și părea suficient de simplu ca să funcționeze în monadul de stat scriind-o astfel:

getCurrentPlayerM = do state <- get
                       return (players state ! on_turn state)

Cu toate acestea, acest lucru provoacă plângeri de la ghc! Nu este o instanță pentru (MonadState GameState m0) care rezultă dintr-o utilizare a `get ', se spune. Am redenumit deja o funcție foarte asemănătoare, cu excepția faptului că nu a fost nullary în forma sa monadică de stat, așa că pe o bănuială, l-am rescris astfel:

getCurrentPlayerM _ = do state <- get
                         return (players state ! on_turn state)

Și destul de sigur, funcționează! Dar, desigur, trebuie să o numesc ca getCurrentPlayerM (), și mă simt un pic prostească făcând asta. Trecerea la un argument a fost ceea ce am vrut să evit în primul rând!

O surpriză suplimentară: privirea la tipul ei în ghci

getCurrentPlayerM :: MonadState GameState m => t -> m P.Player

dar dacă încerc să scriu explicit în codul meu, primesc o altă eroare: "Argumentul non-variabil de tip în constrângerea MonadState GameState m" și o ofertă de extensie de limbă care să o permită. Presupun că este pentru că GameState este un tip și nu un tip de clasă, dar de ce este acceptat în practică, dar nu când încerc să fiu explicit despre asta sunt mai confuz.

Deci, pentru a rezuma:

  1. De ce nu pot scrie funcții nullary în monadul de stat?
  2. De ce nu pot declara tipul de funcție a mea de rezolvat?
0

1 răspunsuri

Problema este că nu scrieți semnături de tip pentru funcțiile dvs. și se aplică restricția monomorfismului.

Când scrieți:

getCurrentPlayerM = ...

scrieți o definiție a valorii constrângerii unare la nivel înalt fără o declarație de tip, astfel încât compilatorul Haskell va încerca să deducă un tip pentru definiție. Cu toate acestea, restricția monomorfismului (literal: restricție cu o singură formă) afirmă că toate definițiile de nivel superior cu constrângeri de tip implicit trebuie să se rezolve la tipuri concrete, adică nu trebuie să fie polimorfe.


Pentru a explica ce vreau să spun, luați acest exemplu mai simplu:

pi = 3.14

Here, we define pi without a type, so GHC infers the type Fractional a => a, i.e. "any type a, as long as it can be treated like a fraction." However, this type is problematic, because it means that pi is not a constant, even though it looks like it is. Why? Because the value of pi will be re-computed depending on what type we want it to be.

Dacă avem (2 :: Double) + pi , pi va fi un Double . Dacă avem (3 :: Float) + pi , pi va fi un Float . De fiecare dată când pi este folosit, trebuie re-calculat (deoarece nu putem stoca versiuni alternative ale pi tipuri, putem?). Acest lucru este bine pentru literal 3.14 , dar dacă doream mai multe zecimale de pi și am folosit un algoritm fictiv care la calculat? Nu vrem să fie recompusă de fiecare dată când pi este utilizat, atunci?

Acesta este motivul pentru care raportul Haskell afirmă că definițiile de tip unary de vârf de nivel superior trebuie să aibă un singur tip (monomorf), pentru a evita această problemă. În acest caz, pi va primi tipul implicit de Double . Puteți modifica tipurile numerice implicite, dacă doriți, utilizând cuvântul cheie implicit :

default (Int, Float)

pi = 3.14 -- pi will now be Float

În cazul tău, totuși, primești semnătura dedusă:

getCurrentPlayerM :: MonadState GameState m => m P.Player

Aceasta înseamnă: "Pentru orice monad de stat care stochează GameState s, extrage un jucător." Cu toate acestea, deoarece restricția monomorfismului se aplică, Haskell este forțat să încerce să facă acest tip non-polimorf, prin alegerea unui tip de beton pentru m . Cu toate acestea, nu se poate găsi una, deoarece nu există niciun tip de implicit pentru monații de stat ca și cum ar fi pentru numere, așa că renunță.

Fie doriți să dați funcției dvs. o semnătură de tip explicită:

getCurrentPlayerM :: MonadState GameState m => m P.Player

... dar va trebui să adăugați extensia de limbă FlexibleContexts Haskell pentru a funcționa, adăugând acest lucru în partea de sus a fișierului:

{-# LANGUAGE FlexibleContexts #-}

Sau, puteți specifica în mod explicit care monad de stat doriți:

getCurrentPlayerM :: State GameState P.Player

De asemenea, puteți dezactiva restricția monomorfismului, adăugând extensia pentru aceasta; este mult mai bine însă să adăugați semnături de tip.

{-# LANGUAGE NoMonomorphismRestriction #-}

PS. If you have a function that takes your state as a parameter, you can use:

value <- gets getCurrentPlayer

De asemenea, trebuie să căutați în Lentile cu Monases de stat , care vă permite să scrieți un cod foarte curat pentru trecerea implicită de stat.

0
adăugat
Afirmația explicativă despre moneda pe care o vreau pare a fi soluția potrivită în cazul meu. Da, am întâlnit deja problema cu înregistrările din înregistrări și am citit puțin despre Lentile (printre altele, atunci când am căutat răspunsul aici, se pare că se recomandă mult!). Multumesc, explicatii excelente!
adăugat autor Harald Korneliussen, sursa