我已经使用Data.Singletons库试验了依赖类型的程序,在文章中开发了长度注释向量,"依赖于单例类型编程,"我遇到了以下问题。
此代码,不包括函数indexI
的定义,GHC 7.6.3中的类型检查,并在其缺席时按预期工作:
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-}
import Data.Singletons
import Data.Singletons.TH
data Nat where
Z :: Nat
S :: Nat -> Nat
deriving Eq
$(genSingletons [''Nat])
data FlipList :: * -> * -> Nat -> * where
Cons :: ((a -> b) -> a -> b) -> FlipList a (a -> b) n -> FlipList a b (S n)
Nil :: FlipList a b Z
type family (m :: Nat) :< (n :: Nat) :: Bool
type instance m :< Z = 'False
type instance Z :< (S n) = 'True
type instance (S m) :< (S n) = m :< n
type family PreMap a b (m :: Nat) :: *
type instance PreMap a b Z = a -> b
type instance PreMap a b (S n) = PreMap a (a -> b) n
type family BiPreMap a b (m :: Nat) :: *
type instance BiPreMap a b m = PreMap a b m -> PreMap a b m
index :: ((m :< n) ~ 'True) => SNat m -> FlipList a b n -> BiPreMap a b m
index SZ (Cons f _) = f
index (SS sm) (Cons _ fl) = index sm fl
indexI :: ((m :< n) ~ 'True, SingI m) => FlipList a b n -> BiPreMap a b m
indexI = withSing index
在包含indexI
之后,GHC会产生两个错误,
Could not deduce (PreMap a b m ~ PreMap a b a0)
from the context ((m :< n) ~ 'True, SingI Nat m)
bound by the type signature for
indexI :: ((m :< n) ~ 'True, SingI Nat m) =>
FlipList a b n -> BiPreMap a b m
和
Could not deduce (PreMap a b m ~ PreMap a b a0)
from the context ((m :< n) ~ 'True, SingI Nat m)
bound by the type signature for
indexI :: ((m :< n) ~ 'True, SingI Nat m) =>
FlipList a b n -> BiPreMap a b m
任一错误的根源似乎是withSing index
一词的类型为FlipList a b n -> BiPreMap a b a0
,并且无法推断a0 ~ m
,GHC无法证明BiPreMap a b m ~ BiPreMap a b a0
。我知道类型家族的类型推断缺乏我们在使用ADTS时所获得的大部分便利(注入性,生成性等),但我对这种情况下究竟是什么问题以及如何规避它的理解非常有限。是否有一些我可以指定的约束可以清除它?
答案 0 :(得分:2)
这里你应该理解的是,你的代码没有任何问题,只是GHC的类型推断不能确定它是如何类型安全的。请注意,通过注释掉indexI
,在GHC中加载代码并询问withSing index
的类型:
*Main Data.Singletons> :t withSing index
withSing index
:: (SingI Nat a, (a :< n) ~ 'True) =>
FlipList a1 b n -> PreMap a1 b a -> PreMap a1 b a
这意味着GHC能够对您的代码进行类型检查,甚至可以推断出与您指定的类型相同的类型(直到alpha等价)。那么为什么不键入 - 检查您的代码?
问题是你的代码没有明确说明应该如何实例化withSing
的类型参数,特别是类型变量a
应该从你的类型实例化到m
签名。可以想象a
应该被实例化为其他内容(例如[m]
或m -> m
),以便您的实现withSing index
具有您指定的类型。 GHC无法确定a
应该实例化为m
,您会得到错误。请注意,GHC不会尝试猜测这种实例,这是一件好事。我们不希望GHC的类型级语言退化为Prolog解释器;)。在我看来,它已经过于接近了。
这意味着您有两种方法可以解决您的问题。上面的user2407038建议了第一个解决方案:使用类型注释告诉GHC应该如何实例化函数withSing
的类型参数a。让我在这里重复他的代码以供参考:
indexI :: forall m n a b . ((m :< n) ~ 'True, SingI m) => FlipList a b n -> BiPreMap a b m
indexI = withSing (index :: SNat m -> FlipList a b n -> BiPreMap a b m)
请注意,您需要在类型签名中使用显式forall
语法,以确保类型签名中的m
在indexI
的实现范围内(查看文档有关更多信息,请参阅GHC的ScopedTypeVariables扩展。)
您的另一种选择是更改代码,以便GHC可以确定如何通过类型推断实例化a
。要理解这一点,请考虑GHC告诉您它无法推断PreMap a b m ~ PreMap a b a0
。这意味着GHC已将withSing index
推断为我在此答案开头向您展示的类型,并尝试查找类型实例化以确定此推断类型如何等于您注释的类型。为此,它尝试解决等式约束BiPreMap a b m ~ BiPreMap a b a0
,将其简化为更简单的约束PreMap a b m ~ PreMap a b a0
。然而,这就是卡住的地方。因为像PreMap这样的类型系列不一定是单射的,所以它不能由此决定m
必须等于a0
。解决此问题的一种方法是将BiPreMap
更改为数据类型或新类型。与类型族不同,数据类型和新类型在其参数中是,并且GHC可以解决约束:
newtype BiPreMap a b m = BiPreMap { getBiPreMap :: PreMap a b m -> PreMap a b m }
index :: ((m :< n) ~ 'True) => SNat m -> FlipList a b n -> BiPreMap a b m
index SZ (Cons f _) = BiPreMap f
index (SS sm) (Cons _ fl) = BiPreMap (getBiPreMap (index sm fl))
indexI :: ((m :< n) ~ 'True, SingI m) => FlipList a b n -> BiPreMap a b m
indexI = withSing index
就是这样,我希望这可以澄清一些正在发生的事情......请注意,您尝试做的“Haskell中的依赖类型编程”类型在Haskell中是非常重要的,您可能会遇到更多一路走来的这种问题。通常,显式类型签名将是您可能遇到的奇怪类型错误的解决方案。显式类型的应用程序也很有用,但据我所知,对GHC的支持仍然缺失或正在进行中。