首页 > 解决方案 > 即使只有一个实例,Haskell 中的类型解析也会报告歧义

问题描述

我正在 Haskell 中尝试传递类型类实例。众所周知,不能在原始类型类(即(C a b, C b c) => C a c)中声明传递实例。因此,我尝试定义另一个代表原始类的传递闭包的类。最小代码如下:

{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
module Ambig where

class Coe a b where
  from :: a -> b

class CoeTrans a b where
  from' :: a -> b

instance CoeTrans a a where
  from' = id

instance (Coe a b, CoeTrans b c) => CoeTrans a c where
  from' = from' . from @a @b

instance Coe Bool Int where
  from False = 0
  from True = 1

instance Coe Int Integer where
  from x = toInteger x

CoeTrans的传递闭包在哪里Coe。但是,当我尝试使用from'inCoeTrans时,它总是报告歧义:

-- >>> from' True :: Integer
-- Ambiguous type variable ‘b0’ arising from a use of ‘from'’
-- prevents the constraint ‘(Coe Bool b0)’ from being solved.
-- Probable fix: use a type annotation to specify what ‘b0’ should be.
-- These potential instance exist:
--   instance Coe Bool Int
--     -- Defined at /Users/t/Desktop/aqn/src/Ambig.hs:21:10

即使实际上只有一个实例。但是根据GHC 文档,如果有一个适用的实例,类型类解析将成功。

为什么会发生这种情况,有没有办法解决传递实例问题?

标签: haskellfunctional-programmingghctypeclass

解决方案


我认为您误解了文档。他们确实说,如果存在一个实例,则给定类型的类型类解析将成功。但在你的情况下,没有给出类型。b0是模棱两可的。

b0编译器在选择 的实例之前需要知道Coe Bool b0,即使当前范围内只有一个实例。这是故意这样做的。其中的关键词是“当前范围”。您会看到,如果编译器只能选择范围内可用的任何内容,您的程序将容易受到范围内细微变化的影响:您可能会更改导入,或者某些导入的模块可能会更改其内部结构。这可能会导致您当前范围内出现或消失不同的实例,这可能会导致您的程序的不同行为而没有任何警告。


如果您真的打算在任何两种类型之间始终最多有一个明确的路径,您可以通过添加一个函数依赖来解决它Coe

class Coe a b | a -> b where
  from :: a -> b

这将产生两个影响:

  1. 编译器会知道它总是b可以通过知道来推断a
  2. 为了促进这一点,编译器将禁止定义具有相同a但不同bs 的多个实例。

现在编译器可以看到,由于from'is的参数Bool,它必须搜索 for Coe Bool bsome的一个实例b,并且从那里它会确定必须是什么b,然后它可以从那里搜索下一个实例,依此类推.


另一方面,如果您真的打算在两个给定类型之间有多个可能的路径,并且编译器只选择一个 - 你就不走运了。编译器原则上拒绝随机选择多种可能性之一 - 请参阅上面的解释。


推荐阅读