2015-03-23 4 views
5

In der Funktion test traversiere ich über eine Liste, erzeuge Linsen von ihren Mitgliedern und drucke dann einige Daten aus. Das funktioniert, wenn ich einen sinnvollen Anrufstil verwende. Es schlägt fehl, wenn ich es point-free mache.Punktfreie Objektiverstellung wird nicht geprüft

Warum ist das der Fall, und wie kann ich dieses Problem lösen?

Es sieht aus wie ich, dass GHC nicht die Information, dass die höherrangige f (im Objektiv) ist ein Functor bei der Verwendung von Punkt-freien Stil, aber ich bin mir nicht sicher.

Ich verwende 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

Ich weiß nicht, die Details, aber ich bin mir ziemlich sicher, dass es etwas mit höherrangigen Arten verursacht einige Typisierung Problemen zu tun. Das Übergeben von Lens-Argumenten kann diese Art von Problem verursachen. Es wird wahrscheinlich funktionieren, wenn Sie 'ALens' anstelle von' Lens' verwenden, da dies so ausgelegt ist, dass es herumgereicht wird. –

+1

@DavidYoung Sie können * es mit 'ALens' machen, aber dann muss die aufgerufene Funktion es explizit zu einem normalen Objektiv" klonen ", bevor es es benutzt. Das ist normalerweise nur für eine Funktion geeignet, die wirklich die vollen Objektiveigenschaften ihres Arguments benötigt. –

+0

@DavidYoung: Ich werde es untersuchen müssen. Ein schneller Google hat keine Tutorials/Beispiele für "ALens" über "Lens" ergeben, aber ich sollte es herausfinden können. Es ist Zeit, den Tag für jetzt zu beenden. –

Antwort

8

Lens' ist eine höherrangige Art und Typinferenz ist mit denen, sehr spröde, und im Grunde funktioniert nur, wenn alle Funktionen, die nehmen höherrangige Argumente haben dafür explizite Signaturen. Dies funktioniert sehr schlecht mit Punkt-freiem Code unter Verwendung von . und dergleichen, die keine derartigen Signaturen haben. (Es wird nur $ hat eine spezielle Hack manchmal mit dieser Arbeit.)

Die lens Bibliothek selbst um diese bekommt, indem sichergestellt wird, dass alle Funktionen, die Verwendung ein Objektiv Argument nicht einen vollständig allgemeinen Linsentyp für sie hat, aber nur ein Typ, der die genaue Objektiveigenschaft angibt, verwenden sie . In Ihrem Fall ist es die printHandle Funktion, die dafür verantwortlich ist. Ihr Code wird kompiliert, wenn Sie seine Unterschrift auf die präziseren ändern

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

ich diese Signatur gefunden, indem das Original zu löschen und mit :t printHandle.

EDIT (und EDIT wieder ALens' hinzufügen): Wenn Sie das denken „Heilung ist schlimmer als die Krankheit“, dann auf Ihre Bedürfnisse eine weitere Option abhängig, was erfordert Sie nicht Ihre Funktion Signaturen ändern, aber die benötigt erfordert, dass Sie eine explizite Konvertierung durchführen, verwenden Sie stattdessen den Typ ALens'. Sie müssen dann zwei Zeilen ändern:

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

ALens' ist ein nicht-höherer Rang Typ, der geschickt konstruiert wurde, so dass es alle Informationen enthält, benötigt mit cloneLens eine allgemeinen Linse daraus zu extrahieren. Aber es immer noch ist ein spezielles Subtyp einer Linse (die Functor wurde gerade besonders geschickt gewählt), so dass Sie nur explizit benötigen Umwandlung vonALens' zu Lens', nicht andersrum.

Eine dritte Option, die den besten nicht für Linsen sein kann, aber die in der Regel für höherrangige Typen in allgemeinen funktioniert, ist, drehen Sie Ihre PlayerLens in ein newtype:

newtype PlayerLens = PL (Lens' GameState Player) 

Natürlich diesem Jetzt müssen Sie Ihr Code an mehreren Stellen ein- und auspacken.getPlayerLens wurde besonders gestört:

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

Ein Fall, wo die Heilung schlimmer sein könnte als die Krankheit. Naja, aber danke für die Info! –

+0

@ThomasEding Eine weitere Alternative, die Sie vielleicht bevorzugen oder nicht ... –

Verwandte Themen