在声明IsString实例时出现“非法实例声明”

41

我正在编写一个使用UTF-16字符串的应用程序,并尝试为其创建一个IsString实例,以利用重载字符串扩展功能:

import Data.Word ( Word16 )
import Data.String ( IsString(fromString) )

type String16 = [Word16]

instance IsString [Word16] where
    fromString = encodeUTF16

encodeUTF16 :: String -> String16

问题是,当我尝试编译该模块时,GHC 7.0.3会抱怨:

Data/String16.hs:35:10:
    Illegal instance declaration for `IsString [Word16]'
      (All instance types must be of the form (T a1 ... an)
       where a1 ... an are *distinct type variables*,
       and each type variable appears at most once in the instance head.
       Use -XFlexibleInstances if you want to disable this.)
    In the instance declaration for `IsString [Word16]'

如果我注释掉实例声明,它就能成功编译。 为什么会被拒绝呢?对于[Char]的实例看起来很相似,但它可以编译通过。我是不是漏掉了什么?

2
你应该考虑使用 text,它在内部使用 UTF-16。或者至少使用一个 [Word16] 的新类型包装器,以避免这种问题和冲突。 - ehird
@ehird 感谢您的建议。我正在尝试实现Java的字符串哈希函数,该函数适用于16位字符。不幸的是,文本包没有一种简单的方法可以在原始Word16上工作,而不必诉诸黑魔法。 - Lambda Fairy
1
如果您导入Data.Text.Internal,则可以访问底层的Array - ehird
嗯,case s of { Text array offs len -> A.toList array offs len } 并不算太糟糕 :) - ehird
你也可以将其编码为UTF-16 ByteString,但这可能对你没有帮助。无论如何,我强烈建议至少在列表周围使用一个新类型。 - ehird
2个回答

105
通过查阅 GHC 手册和 Haskell 维基百科(特别是List instance页面),我更好地理解了这个问题的工作原理。以下是我学到的内容摘要:

问题

Haskell Report 定义了一个实例声明,如下所示:

类型(T u1 … uk)必须采用类型构造函数 T 应用于简单类型变量 u1, … uk的形式;此外,T 不能是类型同义词,并且 ui 必须都是不同的。

加粗部分是让我困惑的限制条件。用英语表达,它们是:

  1. 类型构造函数之后的任何内容必须是类型变量。
  2. 您不能使用类型别名(使用type关键字)绕过规则1.

那么这与我的问题有什么关系呢?

[Word16]只是写[] Word16的另一种方式。换句话说,[]是构造函数,Word16是其参数。

因此,如果我们尝试编写:

instance IsString [Word16]

这与...相同。
instance IsString ([] Word16) where ...

“这样做不会起作用,因为它违反了规则1,编译器友好地指出了这一点。”
“试图将其隐藏在类型同义词中”
type String16 = [Word16]
instance IsString String16 where ...

“也不工作,因为它违反了第二部分。” “现状是,在标准Haskell中无法使[Word16](或任何其他列表)实现IsString。解决方案#1:newtype。 @ehird建议的解决方案是将其包装在newtype中:”
newtype String16 = String16 { unString16 :: [Word16] }
instance IsString String16 where ...

它绕过了限制,因为String16不再是别名,而是一个新类型(请原谅这个双关语)!唯一的缺点是我们现在必须手动封装和解封,这很烦人。

解决方案2:灵活实例

为了摆脱限制,我们可以使用灵活实例,但代价是牺牲可移植性:

{-# LANGUAGE FlexibleInstances #-}

instance IsString [Word16] where ...

这是@[Daniel Wagner]提出的解决方案。
第三种解决方案:等式约束
最后,还有一种使用等式约束的不太便携的解决方案:
{-# LANGUAGE TypeFamilies #-}

instance (a ~ Word16) => IsString [a] where ...

这个方法使用类型推断效果更好,但可能会发生重叠现象。参见Chris Done的相关文章。(顺便说一下,最终我在Data.Text.Internal上编写了一个foldl'的封装器,并在此基础上实现了哈希函数。)

-14
为什么会被拒绝?
因为:
  (All instance types must be of the form (T a1 ... an)
   where a1 ... an are *distinct type variables*,
   and each type variable appears at most once in the instance head.

有什么我错过的吗? 是的:
   Use -XFlexibleInstances if you want to disable this.)

8
我确信任何遇到这个问题的人(包括昨天的我)都不知道“灵活实例”是什么。你能否详细解释一下它的含义? - Lambda Fairy
2
FlexibleInstances 只是放宽了 @DanielWagner 引用的限制; GHC 手册 提供了更多信息 关于替代使用的限制。这里 也许对你有所帮助。 - ehird

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接