为什么这个使用具有重叠实例的类型类的函数在 GHCi 中表现不同?
Why does this function using a typeclass with overlapping instances behave differently in GHCi?
背景
我在 Haskell (GHC 8.6.3) 中编写了以下代码:
{-# LANGUAGE
NoImplicitPrelude,
MultiParamTypeClasses,
FlexibleInstances, FlexibleContexts,
TypeFamilies, UndecidableInstances,
AllowAmbiguousTypes
#-}
import Prelude(Char, Show, show, undefined, id)
data Nil
nil :: Nil
nil = undefined
instance Show Nil where
show _ = "nil"
data Cons x xs = Cons x xs
deriving Show
class FPack f r where
fpack :: f -> r
instance {-# OVERLAPPABLE #-} f ~ (Nil -> r) => FPack f r where
fpack f = f nil
instance (FPack (x -> b) r, f ~ (Cons a x -> b)) => FPack f (a -> r) where
fpack f a = fpack (\x -> f (Cons a x))
此代码背后的想法是生成一个可变元数函数,该函数接受其参数并将它们打包到一个异构列表中。
例如下面的
fpack id "a" "b" :: Cons [Char] (Cons [Char] Nil)
生成列表 Cons "a" (Cons "b" nil)
。
问题
一般来说,我想通过传递 id
作为其 f
参数来调用 fpack
(如上),因此我希望将以下函数定义为 shorthand:
pack = fpack id
如果我将上面的程序加载到 GHCi 中并执行上面的行,包将根据需要定义,其类型(由 :t
给出)为 FPack (a -> a) r => r
。
所以我在我的程序中定义了这样的函数:
pack :: FPack (a -> a) r => r
pack = fpack id
但是在将上述程序加载到 GHCi 中时会出现以下错误:
bugs\so-pack.hs:31:8: error:
* Overlapping instances for FPack (a0 -> a0) r
arising from a use of `fpack'
Matching givens (or their superclasses):
FPack (a -> a) r
bound by the type signature for:
pack :: forall a r. FPack (a -> a) r => r
at bugs\so-pack.hs:30:1-29
Matching instances:
instance [overlappable] (f ~ (Nil -> r)) => FPack f r
-- Defined at bugs\so-pack.hs:24:31
instance (FPack (x -> b) r, f ~ (Cons a x -> b)) =>
FPack f (a -> r)
-- Defined at bugs\so-pack.hs:27:10
(The choice depends on the instantiation of `a0, r')
* In the expression: fpack id
In an equation for `pack': pack = fpack id
|
31 | pack = fpack id
|
这引出了我的问题。为什么这个函数在 GHCi 中定义时有效,但在程序中定义时却不起作用?有没有办法让我在程序中正常工作?如果可以,怎么做?
我的想法
根据我对 GHC 和 Haskell 的了解,此错误是由于 pack
可以解析为两个重叠实例之一,这困扰着 GHC。但是,我认为 AllowAmbiguousTypes
选项应该通过将实例选择推迟到最终调用站点来解决该问题。不幸的是,这显然是不够的。我很好奇为什么,但我更好奇为什么 GHCi 在其 REPL 循环中接受这个定义,但在程序内部时不接受它。
一条切线
我有另一个关于这个程序的问题,它与这个问题的主旨没有直接关系,但我认为在这里问它可能是明智的,而不是针对同一个程序创建另一个问题。
如上例所示,即
fpack id "a" "b" :: Cons [Char] (Cons [Char] Nil)
我必须向 fpack
提供明确的类型签名,以便它按预期工作。如果我不提供一个(即只调用 fpack id "a" "b"
),GHCi 会产生以下错误:
<interactive>:120:1: error:
* Couldn't match type `Cons [Char] (Cons [Char] Nil)' with `()'
arising from a use of `it'
* In the first argument of `System.IO.print', namely `it'
In a stmt of an interactive GHCi command: System.IO.print it
有什么办法可以改变 fpack
的定义,让 GHC 推断出正确的类型签名吗?
您需要手动实例化fpack
。
pack :: forall a r . FPack (a -> a) r => r
pack = fpack @(a->a) @r id
这需要ScopedTypeVariables, TypeApplications, AllowAmbiguousTypes
。
或者,为 id
提供类型。
pack :: forall a r . FPack (a -> a) r => r
pack = fpack (id :: a -> a)
问题是 GHC 看不到它是否应该使用 FPack (a->a) r
约束提供的 fpack
。起初这可能令人费解,但请注意,如果有一些 instance FPack (T -> T) r
可用,fpack (id :: T -> T)
也可以正确生成 r
。由于 id
可以同时是 a -> a
和 T -> T
(对于任何 T
),GHC 不能安全地选择。
在类型错误中可以看到这种现象,因为 GHC 提到了 a0
。该类型变量代表某种类型,可能是 a
,但也可能是其他类型。然后可以尝试猜测为什么代码不强制 a0 = a
,假装周围有其他实例可以代替。
背景
我在 Haskell (GHC 8.6.3) 中编写了以下代码:
{-# LANGUAGE
NoImplicitPrelude,
MultiParamTypeClasses,
FlexibleInstances, FlexibleContexts,
TypeFamilies, UndecidableInstances,
AllowAmbiguousTypes
#-}
import Prelude(Char, Show, show, undefined, id)
data Nil
nil :: Nil
nil = undefined
instance Show Nil where
show _ = "nil"
data Cons x xs = Cons x xs
deriving Show
class FPack f r where
fpack :: f -> r
instance {-# OVERLAPPABLE #-} f ~ (Nil -> r) => FPack f r where
fpack f = f nil
instance (FPack (x -> b) r, f ~ (Cons a x -> b)) => FPack f (a -> r) where
fpack f a = fpack (\x -> f (Cons a x))
此代码背后的想法是生成一个可变元数函数,该函数接受其参数并将它们打包到一个异构列表中。
例如下面的
fpack id "a" "b" :: Cons [Char] (Cons [Char] Nil)
生成列表 Cons "a" (Cons "b" nil)
。
问题
一般来说,我想通过传递 id
作为其 f
参数来调用 fpack
(如上),因此我希望将以下函数定义为 shorthand:
pack = fpack id
如果我将上面的程序加载到 GHCi 中并执行上面的行,包将根据需要定义,其类型(由 :t
给出)为 FPack (a -> a) r => r
。
所以我在我的程序中定义了这样的函数:
pack :: FPack (a -> a) r => r
pack = fpack id
但是在将上述程序加载到 GHCi 中时会出现以下错误:
bugs\so-pack.hs:31:8: error:
* Overlapping instances for FPack (a0 -> a0) r
arising from a use of `fpack'
Matching givens (or their superclasses):
FPack (a -> a) r
bound by the type signature for:
pack :: forall a r. FPack (a -> a) r => r
at bugs\so-pack.hs:30:1-29
Matching instances:
instance [overlappable] (f ~ (Nil -> r)) => FPack f r
-- Defined at bugs\so-pack.hs:24:31
instance (FPack (x -> b) r, f ~ (Cons a x -> b)) =>
FPack f (a -> r)
-- Defined at bugs\so-pack.hs:27:10
(The choice depends on the instantiation of `a0, r')
* In the expression: fpack id
In an equation for `pack': pack = fpack id
|
31 | pack = fpack id
|
这引出了我的问题。为什么这个函数在 GHCi 中定义时有效,但在程序中定义时却不起作用?有没有办法让我在程序中正常工作?如果可以,怎么做?
我的想法
根据我对 GHC 和 Haskell 的了解,此错误是由于 pack
可以解析为两个重叠实例之一,这困扰着 GHC。但是,我认为 AllowAmbiguousTypes
选项应该通过将实例选择推迟到最终调用站点来解决该问题。不幸的是,这显然是不够的。我很好奇为什么,但我更好奇为什么 GHCi 在其 REPL 循环中接受这个定义,但在程序内部时不接受它。
一条切线
我有另一个关于这个程序的问题,它与这个问题的主旨没有直接关系,但我认为在这里问它可能是明智的,而不是针对同一个程序创建另一个问题。
如上例所示,即
fpack id "a" "b" :: Cons [Char] (Cons [Char] Nil)
我必须向 fpack
提供明确的类型签名,以便它按预期工作。如果我不提供一个(即只调用 fpack id "a" "b"
),GHCi 会产生以下错误:
<interactive>:120:1: error:
* Couldn't match type `Cons [Char] (Cons [Char] Nil)' with `()'
arising from a use of `it'
* In the first argument of `System.IO.print', namely `it'
In a stmt of an interactive GHCi command: System.IO.print it
有什么办法可以改变 fpack
的定义,让 GHC 推断出正确的类型签名吗?
您需要手动实例化fpack
。
pack :: forall a r . FPack (a -> a) r => r
pack = fpack @(a->a) @r id
这需要ScopedTypeVariables, TypeApplications, AllowAmbiguousTypes
。
或者,为 id
提供类型。
pack :: forall a r . FPack (a -> a) r => r
pack = fpack (id :: a -> a)
问题是 GHC 看不到它是否应该使用 FPack (a->a) r
约束提供的 fpack
。起初这可能令人费解,但请注意,如果有一些 instance FPack (T -> T) r
可用,fpack (id :: T -> T)
也可以正确生成 r
。由于 id
可以同时是 a -> a
和 T -> T
(对于任何 T
),GHC 不能安全地选择。
在类型错误中可以看到这种现象,因为 GHC 提到了 a0
。该类型变量代表某种类型,可能是 a
,但也可能是其他类型。然后可以尝试猜测为什么代码不强制 a0 = a
,假装周围有其他实例可以代替。