3

背景

我在 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 推断出正确的类型签名?

4

1 回答 1

3

您需要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)也可以正确生成。由于可能是and (对于任何),GHC 不能安全地选择。rinstance FPack (T -> T) rida -> aT -> TT

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

于 2019-05-20T20:14:50.477 回答