2015-03-23 18 views
5

Nella funzione test, ho attraversato un elenco, generato obiettivi dai suoi membri e quindi stampato alcuni dati. Funziona quando uso uno stile di chiamata puntuale. Non riesce a tipografare quando lo faccio senza punti.La creazione di obiettivi senza punto non digita il controllo

Perché è questo il caso e come posso risolvere questo problema?

Sembra a me che GHC non sta mantenendo le informazioni che più alto-classificato f (nella lente) è un Functor quando si utilizza stile libero-punto, ma io non sono troppo sicuro.

sto usando GHC 7.8.3

{-# LANGUAGE RankNTypes #-} 
{-# LANGUAGE TemplateHaskell #-} 

import Control.Lens 
import Control.Monad 
import Data.List 
import Data.Maybe 

type PlayerHandle = String 

data Player = Player { _playerHandle :: PlayerHandle } 
makeLenses ''Player 

data GameState = GameState { _gamePlayers :: [Player] } 
makeLenses ''GameState 

type PlayerLens = Lens' GameState Player 

getPlayerLens :: PlayerHandle -> PlayerLens 
getPlayerLens handle f st = fmap put' get' 
    where 
     players = st^.gamePlayers 
     put' player = let 
      g p = case p^.playerHandle == handle of 
       True -> player 
       False -> p 
      in set gamePlayers (map g players) st 
     get' = f $ fromJust $ find (\p -> p^.playerHandle == handle) players 


printHandle :: GameState -> PlayerLens -> IO() 
printHandle st playerLens = do 
    let player = st^.playerLens 
    print $ player^.playerHandle 


test :: GameState -> IO() 
test st = do 
    let handles = toListOf (gamePlayers.traversed.playerHandle) st 
    -- 
    -- Works: Pointful 
    --forM_ handles $ \handle -> printHandle st $ getPlayerLens handle 
    -- 
    -- Does not work: Point-free 
    forM_ handles $ printHandle st . getPlayerLens 


main :: IO() 
main = test $ GameState [Player "Bob", Player "Joe"] 

Test.hs:45:38: 
    Couldn't match type `(Player -> f0 Player) 
         -> GameState -> f0 GameState' 
        with `forall (f :: * -> *). 
         Functor f => 
         (Player -> f Player) -> GameState -> f GameState' 
    Expected type: PlayerHandle -> PlayerLens 
     Actual type: PlayerHandle 
        -> (Player -> f0 Player) -> GameState -> f0 GameState 
    In the second argument of `(.)', namely `getPlayerLens' 
    In the second argument of `($)', namely 
     `printHandle st . getPlayerLens' 
Failed, modules loaded: none. 
+1

Non conosco i dettagli, ma sono abbastanza sicuro che abbia qualcosa a che fare con tipi ordinati superiori causando alcuni problemi di battitura. Passare argomenti 'Lens' può causare questo tipo di problema. Probabilmente funzionerà se usi 'ALens' invece di' Lens', dal momento che è progettato per essere passato in giro. –

+1

@DavidYoung Si * può * farlo con 'ALens', ma poi la funzione chiamata deve esplicitamente' cloneLens' su un obiettivo normale prima di usarlo. Normalmente ciò è appropriato solo per una funzione che ha veramente bisogno delle caratteristiche lente dell'argomento. –

+0

@DavidYoung: dovrò esaminarlo. Un rapido google non ha dato adito a tutorial/esempi per 'ALens' su' Lens', ma dovrei essere in grado di capirlo. È ora di chiamare il giorno in cui si chiude. –

risposta

8

Lens' è un tipo più alto classifica, e il tipo di inferenza è molto fragile con quelli, e fondamentalmente funziona solo quando tutte le funzioni che assumono gli argomenti di rango superiore hanno firme esplicite per farlo. Questo funziona molto male con il codice point-free usando . e simili, che non hanno tali firme. (Solo $ ha un hack speciale per lavorare a volte con questo.)

La biblioteca lens si aggira questo facendo in modo che tutte le funzioni che uso un argomento lente non si ha un tipo di obiettivo pienamente generale per esso, ma solo un tipo che indica la funzione obiettivo precisa che usano .

Nel tuo caso, è la funzione printHandle che è il colpevole di questo. Il vostro codice verrà compilato se si cambia la sua firma al più precisa

printHandle :: s -> Getting Player s Player -> IO() 

ho trovato questa firma cancellando quello originale e utilizzando :t printHandle.

EDIT (e MODIFICA di nuovo per aggiungere ALens'): Se si ritiene che "la cura sia peggiore della malattia", quindi in base alle proprie esigenze un'altra opzione, che non richiede di modificare le firme delle funzioni, ma quale fa richiedere di fare qualche conversione esplicita, è invece utilizzare il tipo ALens'. È quindi necessario modificare due linee:

type PlayerLens = ALens' GameState Player 
... 
printHandle st playerLens = do 
    let player = st^.cloneLens playerLens 
... 

ALens' è un non-più alto rango tipo che è stato abilmente costruito in modo che contenga tutte le informazioni necessarie per estrarre un obiettivo generale da esso con cloneLens. Ma ancora è un sottotipo speciale di un obiettivo (il Functor è stato appena scelto in modo particolarmente intelligente) quindi è necessaria solo la conversione esplicita daALens' a Lens', non il contrario.

Una terza opzione, che non può essere la migliore per le lenti, ma che di solito funziona per i tipi più alto rango in genere, è quello di trasformare il vostro PlayerLens in un newtype:

newtype PlayerLens = PL (Lens' GameState Player) 

Naturalmente questo ora ha bisogno sia di wrapping che di unwrapping in diversi punti del tuo codice.getPlayerLens era particolarmente perturbato:

getPlayerLens :: PlayerHandle -> PlayerLens 
getPlayerLens handle = PL playerLens 
    where 
     playerLens f st = fmap put' get' 
      where 
       players = st^.gamePlayers 
       put' player = let 
        g p = case p^.playerHandle == handle of 
         True -> player 
         False -> p 
        in set gamePlayers (map g players) st 
       get' = f $ fromJust $ find (\p -> p^.playerHandle == handle) players 
+0

Un caso in cui la cura potrebbe essere peggiore della malattia. Oh bene, ma grazie per le informazioni! –

+0

@ThomasEding Un'altra alternativa che puoi preferire o meno ... –