无点镜头创建不进行类型检查

Point-free lens creation does not type check

在函数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.

Lens' 是更高级别的类型,类型推断对这些类型非常脆弱,并且基本上只有在所有采用更高级别参数的函数都有明确的签名才能这样做时才有效。这对于使用 . 等的无点代码非常糟糕,它们没有这样的签名。 (只有 $ 有一个特殊的 hack 有时可以使用它。)

lens 库本身通过确保 使用 镜头参数的所有函数没有完全通用的镜头类型来解决这个问题,但是只有一种类型表明他们使用.

的精确镜头功能

在您的情况下,printHandle 函数是造成这种情况的罪魁祸首。如果您将其签名更改为更精确的

,您的代码将编译
printHandle :: s -> Getting Player s Player -> IO ()

我通过删除原始签名并使用:t printHandle找到了这个签名。

编辑(并再次编辑以添加 ALens'):如果您认为 "cure is worse than the illness",则根据您的需要另一种选择,它不需要您更改函数签名,但是 要求您进行一些显式转换,而是使用 ALens' 类型。然后你需要改变两行:

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

ALens' 是一个非更高级别的类型,它被巧妙地构造,因此它包含使用 cloneLens 从中提取通用镜头所需的所有信息。但它仍然镜头的特殊子类型(刚刚特别巧妙地选择了Functor)所以你只需要显式转换 ALens'Lens',而不是相反。

第三个选项,可能不是镜头的最佳选择,但通常适用于 general 中的高级类型,是将你的 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