Создание бесточечной линзы не проверяет тип

В функции test я просматриваю список, генерирую линзы из его членов, а затем печатаю некоторые данные. Это работает, когда я использую точечный стиль вызова. Он не проверяет тип, когда я делаю его безточечным.

Почему это так, и как я могу решить эту проблему?

Мне кажется, что GHC не сохраняет информацию о том, что f с более высоким рейтингом (в объективе) является Functor при использовании бесточечного стиля, но я не слишком уверен.

Я использую 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.

person Thomas Eding    schedule 23.03.2015    source источник
comment
Я не знаю подробностей, но я почти уверен, что это как-то связано с типами с более высоким рейтингом, вызывающими некоторые проблемы с набором текста. Передача аргументов Lens может вызвать такую ​​проблему. Вероятно, это сработает, если вы используете ALens вместо Lens, так как это предназначено для передачи.   -  person David Young    schedule 23.03.2015
comment
@DavidYoung Вы можете сделать это с помощью ALens, но тогда вызываемая функция должна явно cloneLens вернуться к обычному объективу, прежде чем использовать его. Обычно это подходит только для функции, которой действительно нужны полные функции линзы ее аргумента.   -  person Ørjan Johansen    schedule 23.03.2015
comment
@DavidYoung: мне нужно изучить это. Быстрый поиск в Google не дал никаких руководств/примеров для ALens над Lens, но я должен понять это. Время, чтобы назвать день уходит на данный момент.   -  person Thomas Eding    schedule 23.03.2015
comment
@DavidYoung Через некоторое время я понял, что ALens лучше подходит для линз, чем мой альтернативный newtype хак, если кто-то настаивает на постоянном типе, поэтому теперь я также включил их в свой ответ. (Я думаю, что использование определенных типов на сайтах использования еще более идиоматично.)   -  person Ørjan Johansen    schedule 23.03.2015


Ответы (1)


Lens' — это тип с более высоким рангом, и вывод типа с ними очень ненадежен и в основном работает только тогда, когда все функции, принимающие аргументы с более высоким рангом, имеют для этого явные сигнатуры. Это очень плохо работает с бесточечным кодом, использующим . и тому подобное, которые не имеют таких сигнатур. (Только у $ есть специальный хак, чтобы иногда работать с этим.)

Сама библиотека lens решает эту проблему, следя за тем, чтобы все функции, которые используют аргумент объектива, не имели для него полностью общий тип объектива, а только тип, который указывает точную функцию объектива, которую они использовать.

В вашем случае виновата функция printHandle. Ваш код скомпилируется, если вы измените его подпись на более точную.

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

Я нашел эту подпись, удалив исходную и используя :t printHandle.

РЕДАКТИРОВАТЬ (и снова РЕДАКТИРОВАТЬ, чтобы добавить ALens'): если вы считаете, что «лекарство хуже болезни», то в зависимости от ваших потребностей другой вариант, который не требует от вас изменения сигнатур функций, но который делает< /em> требует, чтобы вы сделали какое-то явное преобразование, вместо этого используйте тип ALens'. Затем вам нужно изменить две строки:

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

ALens' — это тип не более высокого ранга, который был искусно сконструирован таким образом, что содержит всю информацию, необходимую для извлечения из него общей линзы с помощью cloneLens. Но это по-прежнему является особым подтипом линзы (только что особенно удачно выбран Functor), поэтому вам нужно только явное преобразование из ALens' в Lens', а не наоборот.

Третий вариант, который может быть не лучшим для объективов, но который обычно работает для типов более высокого ранга в целом, заключается в преобразовании вашего PlayerLens в newtype:

newtype PlayerLens = PL (Lens' GameState Player)

Конечно, теперь это требует как обертывания, так и распаковки в нескольких местах вашего кода. getPlayerLens был особенно расстроен:

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
person Ørjan Johansen    schedule 23.03.2015
comment
Тот случай, когда лекарство может быть хуже болезни. Ок, но спасибо за информацию! - person Thomas Eding; 23.03.2015