无点镜头创建无法进行类型检查 [英] Point-free lens creation does not type check

查看:94
本文介绍了无点镜头创建无法进行类型检查的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在功能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'是排名较高的类型,类型推断对此非常脆弱,并且基本上仅在所有带有较高排名参数的函数都具有显式的情况下有效签名.这对于使用.等没有此类签名的无点代码非常不利. (只有$具有特殊的技巧,有时才能使用此技巧.)

lens库本身通过确保所有使用镜头参数的函数都没有完全通用的镜头类型,而只是一种表示精确值的类型来解决此问题.他们使用的镜头功能.

在您的情况下,正是printHandle函数是造成这种情况的罪魁祸首.如果您将其签名更改为更精确,则会编译您的代码

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

我通过删除原始签名并使用:t printHandle找到了该签名.

编辑(并再次编辑以添加ALens'):如果您认为治愈胜于疾病",那么根据您的需要,可以选择另一个选项,它不需要更改功能签名,但是确实需要您进行一些显式转换,而是使用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

In the function test, I traverse over a list, generate lenses from it's members, and then print some data. This works when I use a pointful call style. It fails to typecheck when I make it point-free.

Why is this the case, and how can I solve this problem?

It looks like to me that GHC is not retaining the information that the higher-ranked f (in the lens) is a Functor when using point-free style, but I'm not too sure.

I'm using 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' is a higher ranked type, and type inference is very brittle with those, and basically only works when all functions that take higher-rank arguments have explicit signatures to do so. This works very badly with point-free code using . and the like, which don't have such signatures. (Only $ has a special hack to sometimes work with this.)

The lens library itself gets around this by making sure that all functions that use a lens argument don't have a fully general lens type for it, but only a type which indicates the precise lens feature they use.

In your case, it's the printHandle function which is the culprit for this. Your code will compile if you change its signature to the more precise

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

I found this signature by deleting the original one and using :t printHandle.

EDIT (and EDIT again to add ALens'): If you think the "cure is worse than the illness", then depending on your needs another option, which doesn't require you to change your function signatures, but which does require you to do some explicit conversion, is to use the ALens' type instead. You then need to change two lines:

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

ALens' is a non-higher rank type that has been cleverly constructed so that it contains all the information needed to extract a general lens from it with cloneLens. But it still is a special subtype of a lens (the Functor has just been particularly cleverly chosen) so you only need explicit conversion from ALens' to Lens', not the other way.

A third option, which may not be the best for lenses, but which usually works for higher-rank types in general, is to turn your PlayerLens into a newtype:

newtype PlayerLens = PL (Lens' GameState Player)

Of course this now needs both wrapping and unwrapping in several places in your code. getPlayerLens was particularly disrupted:

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

这篇关于无点镜头创建无法进行类型检查的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆