“ Data.Set String”如何(或为什么)不是单一类型?

时间:2019-01-30 21:02:17

标签: haskell types typeclass

我正在努力研究Haskell,方法是尝试写一些我觉得很有趣的东西,现在我正试图找出如何在Haskell中推导Semiring来解决一组特定的解析问题:

class Semiring s where
    zero, one :: s
    mul, add  :: s -> s -> s

instance Semiring Bool where
    zero = False
    one = True
    add = (||)
    mul = (&&)

instance Semiring (Set String) where
    zero    = empty 
    one     = singleton ""
    add a b = union a b
    mul a b = Data.Set.map (\(c, d) -> c ++ d) $ cartesianProduct a b

Bool({true,false},∨,∧,false,true)版本效果很好。 Int版本也是如此。最后一个称为 Parse Forest ,其表示为(E,∪,·,∅,{<>}),其中E是一组字符串,而{<>}是空字符串的集合。

当我尝试编译它时,我得到:

Rigge…   115  10 error           • Illegal instance declaration for ‘Semiring (Set String)’
(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.

这对我来说没有太大意义。 Set String是一种独特的类型,对,class Semiring的所有操作应完全根据字符串集来表示。

如果需要上下文,则项目位于Rigged Regular Expressions。 Bool版本仅报告正则表达式匹配。一个Int版本会报告正则表达式可以匹配的不同方式的数目(即"a" ~ /(a|a*)/将返回2,因为两个不同且唯一的子表达式匹配); ParseForest应该不返回方法的数目,而应返回所有可能方法的集合,但是不能返回,因为我不明白为什么我不能使用具体的数据类型Set String,而又不能返回具体的数据类型像IntBool这样的数据类型都可以。

2 个答案:

答案 0 :(得分:17)

chi的答案描述了如何通过打开扩展名来做到这一点,这非常好。但是,如果您想知道如果没有此扩展,任何人都会过得怎样,那么有两种方法。

最简单的更改是引入一个新类型包装器,以在定义实例之前自己明确摆脱类型变量。

newtype StringSet = StringSet (Set String)
instance Semiring StringSet where {...}

但是,这当然感觉有些笨拙和原始。

或者,在我看来,您不需要像String这样具体:您的实例适用于任何Monoid类型,不是吗?

instance (Ord a, Monoid a) => Semiring (Set a) where
  zero = empty
  one = singleton mempty
  add = union
  mul a b = Data.Set.map (uncurry (<>)) $ cartesianProduct a b

答案 1 :(得分:10)

关键部分是

of the form (T a1 ... an) where a1 ... an are *distinct type variables*,

您的类型为Set String,所以T = Seta1 = String(以及n=1)也是如此。但是String是类型,而不是类型变量。相反,符合要求的实例应该是

instance (....) => Semiring (Set a) where
   ...

无论如何,这是Haskell2010的古老限制,您可以忽略。在现代GHC Haskell中,您可以打开FlexibleInstances扩展名,并使用自己的实例而不会出现问题。 GHC本身应该建议在错误消息中将其打开。

请注意,如今在严格的Haskell2010中几乎没有人编写程序:有太多的扩展名变得太常用了。可以说,应该对报告进行修订,例如Haskell2020,其中包含了大多数常见的无害扩展,以造福于大国。不过,直到有人真正做到这一点,我们将需要经常打开扩展程序。