haskell - 为什么使用具有重叠实例的类型类的此函数在 GHCi 中表现不同?

标签 haskell ghc typeclass ghci overlapping-instances

背景

我在 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 -> aT -> T (对于任何 T ),GHC 无法安全选择。

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

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

相关文章:

haskell - 在 Haskell 中日志记录功能的不同实现之间切换的有效方法?

Haskell 函数转换为类型

linux - 如何在 Linux Debian Wheezy 上安装 Haskell Platform?

haskell - 为什么 `[1, "a"]::[forall a. Show a => a]` 不允许?

haskell - Ord a 允许我使用 "=="吗?

haskell - 重载(+)

在 Haskell 中解析变量类型的元素

haskell - 在 Haskell 中找不到模块 'System.Console.Readline'

haskell - 在 ghci 中加载已编译模块时出现“加载接口(interface)失败”错误

haskell - GHC 链接问题