2016-06-10 14 views
6

Sto provando a seguire il movimento di un oggetto, su un piano 2D, a cui è stata data una lista di comandi "avanti, sinistra o destra".Monumento di stato Haskell in movimento di traslazione in 2 dimensioni

Finora ho le funzioni che contengono le componenti di uno stato dell'oggetto (direzione, posizione e spostamenti) e restituiscono lo stato finale dopo che tutte le mosse sono state completate o tutte le posizioni passate lungo il percorso.

Lo stato dell'oggetto è nella forma Sstate (Dir dx dy) (Pos px py) [m] e un movimento consiste nell'applicare ricorsivamente testa della lista di mosse per generare nuovi stati

cioè Sstate (Dir 1 0) (Pos 0 0) "fff" -> Sstate (Dir 1 0) (Pos 0 1) "ff"

type Mov = Char 

data Pos = Pos Int Int 
deriving (Show, Eq) 

data Dir = Dir Int Int 
deriving (Show, Eq) 

data Sstate = Sstate Dir Pos [Mov] 
deriving (Show, Eq) 

move :: Sstate -> Sstate 
move (Sstate (Dir dx dy) (Pos px py) [] ) = Sstate (Dir dx dy) (Pos px py) [] 
move (Sstate (Dir dx dy) (Pos px py) (m:ms)) 
| m == 'f' = ss dx dy (dx + px) (dy + py) ms 
| m == 'l' = ss (-dy) dx px  py  ms 
| m == 'r' = ss dy (-dx) px  py  ms 
| otherwise = ss dy dx px  py  [] 
where 
    ss a b c d = Sstate (Dir a b) (Pos c d) 

end :: Dir -> Pos -> [Mov] -> Sstate 
end d p ms = iterate move initialState !! n 
where 
    initialState = Sstate d p ms 
    n = length ms + 1 

position :: Sstate -> Pos 
position (Sstate _ p _) = p 

route :: Dir -> Pos -> [Mov] -> [Pos] 
route d p ms = map position . take n . iterate move $ initialState 
where 
    initialState = Sstate d p ms 
    n = length ms + 1 

dando

λ: let x = Sstate (Dir 0 1) (Pos 0 2) "ff" 

λ: move x 
Sstate (Dir 0 1) (Pos 0 3) "f" 

λ: end (Dir 0 1) (Pos 0 2) "ff" 
Sstate (Dir 0 1) (Pos 0 4) "" 

λ: route (Dir 0 1) (Pos 0 2) "ff" 
[Pos 0 2,Pos 0 3,Pos 0 4] 

Questo sembra funzionare ma sembra anche che questo sia qualcosa che richiede lo State monad. Trovo che lo State monad confonda ma ritengo che mi aiuterebbe a capire se qualcuno sarebbe così gentile da mostrare come potrebbe essere usato qui.

+0

Leggere [questo tutorial] (http://learnyouahaskell.com/ for-a-few-monads-more) su alcune monadi, inclusa la monade 'State'. – Bakuriu

+0

Quello che ho trovato confuso con la monade di stato è che lo stato stesso non è in realtà la monade. cos'è un xmonad è una funzione che modifica lo stato e restituisce un valore: s -> (a, s) – mb14

+0

@ mb14 Un modo per capire è che lo stato è sia un parametro aggiuntivo che un valore di ritorno aggiuntivo; una funzione di tipo 'a -> b' diventa una funzione di tipo' a -> s -> (b, s) '. Currying ti fa pensare a questo come prendendo un valore di tipo 'a' e restituendo una nuova funzione che, se data uno stato, può restituire un valore di tipo' b' e un nuovo stato ('a -> (s -> (b, s)). "La monade, tramite l'operatore' >> = ', è proprio ciò che consente di concatenare tali funzioni insieme. In fin dei conti, si finisce con una funzione di tipo' s -> (t, s) ', che trasforma uno stato iniziale in un valore di tipo 't'. – chepner

risposta

1

Ecco un semplice codice "di avviamento" che estende il modulo con alcune riformulazioni in termini di stato. Avrai bisogno di guardare un tutorial come il capitolo LYAH mentre sto giocando con loro, penserei. Tralascio le firme, che diventano sempre più complicate, ma interrogare i tipi in ghci sarà istruttivo. È necessario aggiungere

import Control.Monad.State 
import Control.Monad.Writer -- for the position-remembering example 

Poi il seguente dovrebbe tutto il lavoro utilizzando la definizione di move

step = do      -- step the state once with your `move`, 
sstate <- get     -- whatever the state is 
put (move sstate) 
-- this little program could also be written: `modify move` which shows the 
-- link between what you wrote and State a little more clearly 

steps = do      -- repeatedly apply `step` to the state 
    Sstate _ _ ls <- get   -- til there are no moves, then stop 
    if null ls 
    then return()  -- could be: unless (null ls) $ do step ; steps 
    else step >> steps 

stepsIO = do      -- do all steps as with `steps`, but print 
    [email protected](Sstate a b ls) <- get -- the current state at each state update 
    liftIO $ print sstate 
    if null ls then liftIO (putStrLn "Done!") 
      else step >> stepsIO 

stepsPrintPosition = do   -- do all steps as with `steps`, printing 
    Sstate _ b ls <- get   -- only position at each state update 
    liftIO $ do putStr "current position: " 
       print b 
    if null ls then liftIO (putStrLn "Done!") 
      else do step 
        stepsPrintPosition 

stepsAccumulatePositions = do -- move through all states as with `steps` 
    [email protected](Sstate a b ls) <- get -- but use `tell` to keep adding the current 
    tell [b]      -- position to the underlying list 
    if null ls then return()  -- of positions 
      else step >> stepsAccumulatePositions 

example = Sstate (Dir 0 1) (Pos 0 2) "ffff" 

Per usare cose come step, steps, stepsIO ecc, applichiamo runState; questo ci dà una funzione da uno stato a un nuovo stato

runStateT :: StateT s m a -> s -> m (a, s) 

Questo naturalmente è solo la funzione di accesso per la definizione newtype

newtype StateT s m a = StateT {runStateT :: s -> m (a, s)} 

La confezione ci permette di scrivere fantasia s -> m (a, s) cose, utilizzando semplici s -> m (a, s) bit, ma sotto il cappuccio newtype, è sempre solo una funzione s -> m (a, s) che stiamo scrivendo nella notazione.

Ovviamente, una volta che scegliamo con runStateT e abbiamo la nostra funzione s -> m (a, s), è necessario fornirgli uno stato iniziale. E 'più facile per vedere come funziona mediante test in ghci

>>> example 
Sstate (Dir 0 1) (Pos 0 2) "ffff" 

>>> runStateT step example   -- we step the state once with move 
((),Sstate (Dir 0 1) (Pos 0 3) "fff") 

>>> runStateT steps example   -- we keep stepping till there are no moves 
((),Sstate (Dir 0 1) (Pos 0 6) "") 

>>> runStateT stepsIO example   -- we print state at each state update 
Sstate (Dir 0 1) (Pos 0 2) "ffff" 
Sstate (Dir 0 1) (Pos 0 3) "fff" 
Sstate (Dir 0 1) (Pos 0 4) "ff" 
Sstate (Dir 0 1) (Pos 0 5) "f" 
Sstate (Dir 0 1) (Pos 0 6) "" 
Done! 
((),Sstate (Dir 0 1) (Pos 0 6) "") 

>>> runStateT stepsPrintPosition example -- print position only at state updates 
current position: Pos 0 2 
current position: Pos 0 3 
current position: Pos 0 4 
current position: Pos 0 5 
current position: Pos 0 6 
Done! 
((),Sstate (Dir 0 1) (Pos 0 6) "") 


-- the WriterT examples accumulate a 'monoid' of things you keep 
-- adding to with `tell xyz` Here we accumulate a [Position] 
-- execXYZ and evalXYZ, where they exist, return less information than runXYZ 

>>> runWriterT $ runStateT stepsAccumulatePositions example 
(((),Sstate (Dir 0 1) (Pos 0 6) ""),[Pos 0 2,Pos 0 3,Pos 0 4,Pos 0 5,Pos 0 6]) 

>>> execWriterT $ evalStateT stepsAccumulatePositions example 
[Pos 0 2,Pos 0 3,Pos 0 4,Pos 0 5,Pos 0 6] 

Nel codice di cui sopra che sto usando le mtl typeclasses e quindi utilizzando runStateT e runWriterT di "interpretare" o specializzarsi classe che coinvolge le firme. Si tratta di tipi di calcestruzzo StateT e WriterT definiti in Control.Monad.Trans.{State/Writer} Uno potrebbe omettere le classi e scrivere solo direttamente con quei tipi concreti, importando tali moduli. L'unica differenza potrebbe essere che devi fare lift $ tell [b] nel caso in cui combino due effetti, stato e scrittura o qualsiasi altra cosa tu voglia chiamarlo.

C'è molto da dire sull'analisi dello stato con cui si sta lavorando, ma emergerà come si potrebbe rielaborarlo, se si pensa a quanto sopra.

2

Nota che non si direttamente necessario utilizzare il State Monade qui, come end e route può essere scritta utilizzando foldl' e scanl' una volta si considera un Mov come qualcosa che opera su uno stato, piuttosto che essere parte dello stato. Registra anche la sintassi per Sstate.

type Mov = Char 
data Pos = Pos Int Int deriving (Show, Eq) 
data Dir = Dir Int Int deriving (Show, Eq) 
data Sstate = Sstate {direction :: Dir, position :: Pos} deriving (Show, Eq) 

-- A helper to simplify move 
changeDir :: Mov -> Dir -> Dir 
changeDir 'l' (Dir x y) = Dir (-y) x 
changeDir 'r' (Dir x y) = Dir y (-x) 
changeDir m (Dir x y) = Dir y x 

move :: Mov -> Sstate -> Sstate 
move 'f' [email protected](Sstate (Dir dx dy) (Pos px py)) = oldState { position = Pos (dx + px) (dy + py) } 
move m [email protected](Sstate dir _) = oldState { direction = changeDir m dir } 

end :: [Mov] -> Sstate -> Sstate 
end ms initialState = foldl' (flip move) initialState ms 

route :: [Mov] -> Sstate -> [Pos] 
route ms initialState = map position $ scanl' (flip move) initialState ms 

Poi i tuoi esempi diventano:

λ: let x = Sstate {direction = Dir 0 1, position = Pos 0 2} 

λ: move 'f' x 
Sstate {direction = Dir 0 1, position = Pos 0 3} 

λ: end "ff" x 
Sstate {direction = Dir 0 1, position = Pos 0 4} 

λ: route "ff" x 
[Pos 0 2,Pos 0 3,Pos 0 4] 

Lascio come esercizio (o per qualcuno più esperto e meno pigro di me) per adattare

move :: Mov -> Sstate -> Sstate 
end :: [Mov] -> Sstate -> Sstate 
route :: [Mov] -> Sstate -> [Pos] 

a

move :: Mov -> State Sstate() 
-- or possibly move :: Mov -> State Sstate Sstate ? 
-- Like I said, more knowledgeable than me 
end :: [Mov] -> State Sstate Sstate 
route :: [Mov] -> State Sstate [Pos] 
+0

Grazie. Davvero utile: vedere il codice refactored da qualcuno che sa cosa stanno facendo aiuta davvero il mio processo di apprendimento – matthias

+0

Ironia della sorte, rifattorizzare i punti di partenza di altre persone è come Sto facendo la maggior parte del mio apprendimento. – chepner

+0

@chepner, '()' sembra il più semplice e il più idiomatico per me. – dfeuer