背景
我在 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
,所以我希望定义以下内容函数作为简写:
pack = fpack id
如果我将上述程序加载到 GHCi 中并执行上述行,pack 将根据需要进行定义,其类型(由 :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。但是,我认为 AllowAmbigouslyTypes
选项应该通过将实例选择推迟到最终调用站点来解决该问题。不幸的是,这显然还不够。我很好奇为什么,但我更好奇为什么 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
的问题由FPack (a->a) r
提供约束。一开始这可能令人困惑,但请注意 fpack (id :: T -> T)
也可以正确生成r
如果有的话instance FPack (T -> T) r
可用的。自 id
可能都是a -> a
和T -> T
(对于任何 T
),GHC 无法安全选择。
我们可以在类型错误中看到这种现象,因为 GHC 提到 a0
。该类型变量代表某种类型,可能是 a
,但也可能是其他东西。然后,人们可以尝试猜测为什么代码不强制 a0 = a
,假装周围还有其他实例可以替代。
关于haskell - 为什么使用具有重叠实例的类型类的此函数在 GHCi 中表现不同?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56227475/