无点镜头创建不进行类型检查
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
在函数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