类型族实例中的类型级约束

时间:2016-06-29 20:30:45

标签: haskell type-constraints type-families

是否可以为参数化数据设置类型同义词系列,例如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.

我很感激这件事的任何领导或暗示。

2 个答案:

答案 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中的指南设计她的类型系列,则绝对建议使用。