是否可以为参数化数据设置类型同义词系列,例如Data.Param.FSVec?
理想情况下,我希望编译:
class A e where
type Arg e a
f :: (Arg e a -> b) -> e a -> e b
instance A X where
type Arg X a = Nat size => FSVec size a
f = {- implementation -}
我尝试了几种解决方法,例如在新类型中包装FSVec size a
或约束同义词,但似乎我无法得到任何合理的权利。
A
是先前定义的类(例如):
class OldA e where
f :: (Maybe a -> b) -> [e (Maybe a)] -> [e b]
继承OldA
的类型示例是:
data Y a = Y a
instance Functor Y where
fmap f (Y a) = Y (f a)
instance OldA Y where
f = fmap . fmap
我想扩展这个类,以便能够为f
表达更多通用函数参数。我们假设我们有X
类型和相关函数fIndependent
:
import qualified Data.Param.FSVec as V
import Data.TypeLevel hiding ((==))
data X a = X a deriving Show
fromX (X a) = a
fIndependent :: (Nat size) => (V.FSVec size (Maybe a) -> b) -> [X (Maybe a)] -> [X b]
fIndependent _ [] = []
fIndependent f xs = let x' = (V.reallyUnsafeVector . take c . fmap fromX) xs
xs' = drop c xs
c = V.length x'
in if c == length (V.fromVector x') then X (f x') : fIndependent f xs' else []
fIndependent
本身就是理智的。使用函数
test :: V.FSVec D2 x -> Int
test a = V.length a
将授予结果:
>>> fIndependent test $ map (X . Just) [1,2,3,4,5,6,7,8,9]
[X 2, X 2, X 2, X 2]
好的,现在如何扩展OldA
?最自然的"我想到的一件事就是让班级A
具有类型同义词系列Arg e a
,如下所示。
class NewA e where
type Arg e a
f :: (Arg e a -> b) -> [e (Maybe a)] -> [e b]
转换所有现有实例很简单:
instance NewA Y where
type Arg Y a = Maybe a
f = fmap . fmap -- old implementation
要表达fIndependent
,因为f是困难的部分,因为只需添加
instance NewA X where
type Arg X a = (Nat size) => FSVec size (Maybe a) -- wrong!!!
f = {- same as fIndependent -}
不起作用。这就是我遇到的麻烦。
我看到的大多数解决方案都建议将FSVec
包裹在newtype
内。这样做没有用,因为以下代码:
{-# LANGUAGE RankNTypes #-}
newtype ArgV a = ArgV (forall rate.Nat rate => V.FSVec rate (Maybe a))
instance NewA X where
type Arg X a = ArgV a
g f xs = let x' = (V.reallyUnsafeVector . take c . fmap fromX) xs
xs' = drop c xs
c = V.length x'
in if c == length (V.fromVector x') then X (f $ ArgV x') : g f xs' else []
类型推断系统似乎丢失了有关size
的信息:
Couldn't match type ‘s0’ with ‘rate’ …
because type variable ‘rate’ would escape its scope
This (rigid, skolem) type variable is bound by
a type expected by the context: Nat rate => V.FSVec rate (Maybe a)
Expected type: V.FSVec rate (Maybe a)
Actual type: V.FSVec s0 (Maybe a)
Relevant bindings include
x' :: V.FSVec s0 (Maybe a)
(bound at ...)
In the first argument of ‘Args’, namely ‘x'’
In the second argument of ‘($)’, namely ‘Args x'’
Compilation failed.
我很感激这件事的任何领导或暗示。
答案 0 :(得分:2)
您似乎正在使用课程Nat :: k -> Constraint
和数据类型FSVec :: k -> * -> *
。数据类型受旧DatatypeContexts
扩展名约束。
{-# LANGUAGE DatatypeContexts #-}
class Nat n
data Nat n => FSVec n a = FSVec -- ...
您有一个现有的课程A :: (* -> *) -> Constraint
,您希望为其编写FSVec
个实例。
class A e where
--- ...
f :: ( {- ... -} b) -> e a -> e b
但是FSVec
永远不会有A
个实例,因为它是一种不匹配。类A
需要类型为* -> *
的类型参数,但FSVec
具有类型k -> * -> *
。您已经遇到问题,甚至还没有使用类型系列。如果你试图这样做(挥手告诉你现在的类型族论证)
data X = X
instance A (FSVec) where
type Arg FSVec a = X
f = undefined
您收到编译错误。
Expecting one more argument to `FSVec'
The first argument of `A' should have kind `* -> *',
but `FSVec' has kind `* -> * -> *'
In the instance declaration for `A (FSVec)'
此前的所有内容,包括编译器错误,都是传达您遇到的问题的有用信息,对寻求帮助非常有用。
幸运的是,这是一个非常容易解决的问题。如果您选择一些自然数n
,则FSVec n
具有* -> *
种类,它与A
的类型参数类型相匹配。您可以开始撰写instance A (FSVec n)
instance A (FSVec n) where
f = -- ...
当您使用类型族
重新引入完整的类定义时{-# LANGUAGE TypeFamilies #-}
class A e where
type Arg e a
f :: (Arg e a -> b) -> e a -> e b
解决方案仍然是为A
而不是FSVec n
编写FSVec
个实例。既然n
已经进入了instance
声明,那么就可以捕获所需的Nat n
上下文了。
instance Nat n => A (FSVec n) where
type Arg (FSVec n) a = FSVec n a
f = undefined -- ...
答案 1 :(得分:0)
Cirdec的回答解释了其中一个问题,但其解决方案并未完全回答所发布的问题。该问题要求X
类的实例A
,并带有FSVec
类型的同义词。
此处阻止定义type Arg X = FSVec size a
(在任何可能的配置中)的首要问题是type families are not injective。了解这一点并遵循Cirdec的推理,我可以想出一个解决方法来实现这个目标:包括代理" context" X
类型的变量,以克服上述问题。
data X c a = X a
instance (Nat n) => A (X n) where
type (X n) a = FSVec n a
f = {- same as fIndependent -}
当然,这是一个快速修复,适用于最小的示例(即它回答发布的问题),但在编写多个函数(例如f
)时可能无法很好地扩展,因为在推断之间可能会出现类型冲突"上下文"
我能想到的最佳解决方案是为每个实例添加constraint synonym(由this answer建议),例如:
import qualified Data.Param.FSVec
import Data.TypeLevel
import GHC.Exts -- for Constraint kind
class A e where
type Arg e context a
type Ctx e context :: Constraint
f :: (Ctx e context) => (Arg e context a -> b) -> [e (Maybe a)] -> [e b]
instance A Y where
type Arg Y c a = Maybe a
type Ctx Y c = ()
f = {- same as before -}
instance A X where
type Arg X size a = V.FSVec size (Maybe a)
type Ctx X size = Nat rate
f = {- same as fIndependent -}
但是,由于类型家族臭名昭着的非注入性(例如Could not deduce: Arg e context0 a ~ Arg e context a
),我们将不得不处理由此产生的模糊类型。在这种情况下,证明注入性必须使用GHC 8.0中提供的TypeFamilyDependencies扩展名(基于injective type families)手动完成,并将Arg
定义为:
type family Arg (e :: * -> *) context = (r :: * -> *) | r -> context
当然,如果类型族的设计不是单射的(这是我的情况),这是不可能的,但它是迄今为止最干净的解决方案。如果可以使用provided paper中的指南设计她的类型系列,则绝对建议使用。