背景
我在 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)
问题
通常,我想通过将 fpack 作为其 id 参数(如上)传递来调用 f,因此我希望将以下函数定义为速记:
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。但是,我认为 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 约束提供的 FPack (a->a) r。起初这可能令人费解,但请注意,如果有一些 fpack (id :: T -> T) 可用,r 也可以正确生成 instance FPack (T -> T) r。由于 id 可以同时是 a -> aT -> T (对于任何 T ),GHC 不能安全地选择。

可以在类型错误中看到这种现象,因为 GHC 提到了 a0 。该类型变量代表某种类型,可能是 a ,但也可能是其他类型。然后可以尝试猜测为什么代码不强制 a0 = a ,假装周围有其他可以使用的实例。

关于haskell - 为什么此函数使用具有重叠实例的类型类在 GHCi 中表现不同?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/56227475/

10-12 06:16