2015-04-06 11 views
5

ich mit freiem Monaden und Objektiv bin herumschlagen, die freie Monade mit meiner eigenen Version des IO Monade zu erstellen:Schwierigkeit mit Zoom und frei Monaden

data MyIO next 
    = LogMsg String next 
    | GetInput (String -> next) 
    deriving (Functor) 

ich dies von einem Zustand Monade oben am Stapel etwa so: FreeT MyIO (State GameState) a wo GameState ist:

data GameState = GameState { _players :: [PlayerState] } 

Nun, was ich möchte, ist ein Weg, um ein PlayerState von einem GameState Kontext „Zoom-in“. Etwas wie folgt aus:

zoomPlayer :: Int -> FreeT MyIO (State PlayerState) a -> FreeT MyIO (State GameState) a 
zoomPlayer i prog = hoistFreeT (zoom (players . element i)) prog 

Aber ich bin immer diese Fehlermeldung:

No instance for (Data.Monoid.Monoid a1) 
    arising from a use of ‘_head’ 

Dieser Fehler auf die Tatsache, dass scheint players . element i ein Traversal ist; wenn ich den Listenaspekt von _players entferne und normales Objektiv verwende, funktioniert der Code.

Irgendwelche Ideen zum Schreiben dieser Funktion?

+5

Wäre es nicht einfacher, stattdessen 'StateT GameState (Free MyIO) a' zu haben? Wenn Sie später 'Free MyIO' für' IO' tauschen wollen, dann wäre dies der übliche Weg, da es keine Transformerversion von 'IO' gibt. – bheklilr

+1

Das Problem rührt auch daher, dass das Indexieren einer Liste keine sichere Operation ist. Die Linsenbibliothek möchte, dass Sie einen Typ zurückgeben, der voreingestellt werden kann, und die Standardtypenklasse dafür ist "Monoid". Wenn Sie einfach 'Monoid a =>' zur Typ-Signatur von 'zoomPlayer' hinzufügen, werden Sie festgelegt. Das schränkt natürlich das ein, was Sie zurückgeben können, aber Sie müssen es tun, es sei denn, Sie möchten mehr Code schreiben. – bheklilr

+0

@bheklir Mein schlechtes, das 'a' im Fehler war ein Tippfehler; es war wirklich 'a1'. Die Verwirrung tut mir leid. Es scheint, als wäre 'FreeF' das Ding, von dem es erwartet, dass es ein Monoid ist, aber ich habe keine Möglichkeit, eine Instanz dafür zu schreiben. Vielleicht könnte ich es vorübergehend in eine Liste einpacken und es dann auspacken. – Pubby

Antwort

2

Wenn Sie sicher, dass Sie nie Index in eine nicht existierende Spieler und nicht ein wenig unsafety dagegen haben, können Sie die unsafeSingular combinator verwenden, um eine Traversal in eine Lens zu drehen, wie folgt aus:

zoomPlayer :: Int -> FreeT MyIO (State PlayerState) a -> FreeT MyIO (State GameState) a 
zoomPlayer i prog = hoistFreeT (zoom (players . unsafeSingular (element i))) prog 

(auch würde ich vielleicht ix statt element verwenden, aber das für das Problem nicht verwandt ist.)

Wir auch sichere Indizierung Linsen für immer unendliche Sequenzen, wie Ströme definiert Cofree vommit konstruierenPaket:

import Control.Lens (Lens', _Wrapped') 
import Control.Comonad.Cofree (Cofree, telescoped) 
import Data.Functor.Identity 
import Control 

sureIx :: Int -> Lens' (Cofree Identity a) a 
sureIx i = telescoped $ replicate i _Wrapped' 

Aber ein Spiel ist unwahrscheinlich, endlose Spieler zu haben.