如何在Haskell类型签名中从Lens为At(类映射类型)指定类型参数?
我希望约束键类型为如何在Haskell类型签名中从Lens为At(类映射类型)指定类型参数?,haskell,haskell-lens,Haskell,Haskell Lens,我希望约束键类型为ImageId,值类型为Sprite,同时通过。这可能吗?我似乎得到了一种不匹配,基于类型签名,我不知道如何解决它。我的例子是: data Game m e = Game { initial :: e, -- ... sprites :: (At m) => IO (m ImageId Sprite) } 我的错误: * Expected kind `* -> * -> *', but `m' has kind `*' * In
ImageId
,值类型为Sprite
,同时通过。这可能吗?我似乎得到了一种不匹配,基于类型签名,我不知道如何解决它。我的例子是:
data Game m e = Game {
initial :: e,
-- ...
sprites :: (At m) => IO (m ImageId Sprite)
}
我的错误:
* Expected kind `* -> * -> *', but `m' has kind `*'
* In the first argument of `IO', namely `(m ImageId Sprite)'
In the type `(At m) => IO (m ImageId Sprite)'
In the definition of data constructor `Game'
|
64 | sprites :: (At m) => IO (m ImageId Sprite)
| ^^^^^^^^^^^^^^^^
At m
提供At::Index m->Lens'm(可能是(IxValue m))
。请注意,Lens'm
意味着m
是一个像Int
或Map ImageId Sprite
这样的具体类型,而不是像Map
这样的类型构造函数。如果您想说m ImageId Sprite
是“类似地图的”,那么您需要以下3个约束:
:提供At(m ImageId Sprite)
用于索引和更新At
:用于索引Index(m ImageId Sprite)~ImageId
s的键是m ImageId Sprite
sImageId
:aIxValue(m ImageId Sprite)~Sprite
中的值是m ImageId Sprite
sSprite
游戏
中设置此约束(尽管它仍然是错误的):
请注意,我说的是m ImageId Sprite
a bazillion times,但我没有将m
应用于其他(或更少)参数。这是一条你实际上不需要抽象的线索,m::*->*->*->*
(类似于Map
)。您只需要在m::*
上进行抽象
-- still wrong, though
type IsSpriteMap m = (At m, Index m ~ ImageId, IxValue m ~ Sprite)
data Game m e = Game {
initial :: e,
-- ...
sprites :: IsSpriteMap m => IO m
}
这很好:如果您曾经为这个数据结构制作过专门的映射,比如
data SpriteMap
instance At SpriteMap
type instance Index SpriteMap = ImageId
type instance IxValue SpriteMap = IxValue
您将无法将其用于过于抽象的游戏
,但它正好适合作为游戏SpriteMap e
的较不抽象的游戏
但这仍然是错误的,因为约束位于错误的位置。你在这里做的是这样说:如果你有一个游戏me
,你可以得到一个m
,如果你证明m
是mappish。如果我想创建一个游戏me
,我没有义务证明m
是mappish。如果你不明白为什么,想象一下如果你能用上面的->
替换=>
。调用sprites
的人正在传递一个证明,证明m
就像一张地图,但是游戏本身并不包含证明
如果要将m
作为Game
的一个参数,只需编写:
data Game m e = Game {
initial :: e,
-- ...
sprites :: IO m
}
并编写需要使用m
作为映射的每个函数,如:
doSomething :: IsSpriteMap m => Game m e -> IO ()
或者,您可以使用存在量化:
data Game e = forall m. IsSpriteMap m => Game {
initial :: e,
-- ...
sprites :: IO m
}
要构建一个游戏e
,您可以使用iom
类型的任何东西来填充精灵
,只要ispritemap m
。当您在模式匹配中使用Game e
时,模式匹配将绑定一个(未命名)类型变量(我们称之为m
),然后它将为您提供IO m
和IsPritemap m
的证明
doSomething :: Game e -> IO ()
doSomething Game{..} = do sprites' <- sprites
imageId <- _
let sprite = sprites'^.at imageId
_
doSomething::游戏e->IO()
doSomething Game{..}=do sprites'我试图用and解决这个问题
首先,我在主库中输入以下“Mappy.hsig”签名:
{-# language KindSignatures #-}
{-# language RankNTypes #-}
signature Mappy where
import Control.Lens
import Data.Hashable
data Mappy :: * -> * -> *
at' :: (Eq i, Ord i, Hashable i) => i -> Lens' (Mappy i v) (Maybe v)
我无法直接使用At
类型类,因为
然后我让库代码导入抽象签名,而不是具体类型:
{-# language DeriveGeneric #-}
{-# language DeriveAnyClass #-}
module Game where
import Data.Hashable
import GHC.Generics
import Mappy (Mappy,at')
data ImageId = ImageId deriving (Eq,Ord,Generic,Hashable)
data Sprite = Sprite
data Game e = Game {
initial :: e,
sprites :: IO (Mappy ImageId Sprite)
}
库中的代码不知道Mappy
的具体类型,但它知道当键满足约束时,at'
函数可用。请注意,Game
未使用地图类型参数化。取而代之的是,整个库都有一个签名,该签名必须由库的用户稍后填写,从而使整个库变得不确定
在一个(或完全独立的包)中,我定义了一个与签名同名的实现模块:
{-# language RankNTypes #-}
module Mappy where
import Data.Map.Strict
import Control.Lens
import Data.Hashable
type Mappy = Map
at' :: (Eq i, Ord i, Hashable i) => i -> Lens' (Mappy i v) (Maybe v)
at' = at
可执行文件取决于主库和实现库。主库中的签名“洞”会自动填充,因为有一个同名的实现模块,并且它包含的声明满足签名
module Main where
import Game
import qualified Data.Map
game :: Game ()
game = Game () (pure Data.Map.empty)
此解决方案的一个缺点是,它需要一个键类型的Hashable
实例,即使在示例中实现没有使用它。但是您需要它来允许以后“填充”基于散列的容器,而无需修改签名或导入它的代码。答案非常好。最简单的选择是根本不使用参数,sprites::Map ImageId IxValue
。这是一个非常清楚的答案,我学到了很多。在考虑了这两个建议之后,我有一个问题:为什么不选择所有的?如果API的这一部分再次更改,那么只需要更改代码的一个方面(好吧,希望只有一个部分)。@bbarker有些事情你可以用一个来做,而用另一个来做则不行。如果您只想要它们共享的功能,那么在函数上添加上下文是最简单的解决方案(更少的语言扩展)。
module Main where
import Game
import qualified Data.Map
game :: Game ()
game = Game () (pure Data.Map.empty)