5

这个问题最初是在我研究动态 JS 类型验证器时出现的,它依赖于字典传递样式作为一种相当简单的类型类机制,但我认为它也适用于 Haskell 或其他具有类型类机制的语言。

起初,我认为在具有字典传递样式的设置中允许每个类型有多个类型类实例并不是什么大问题,因为我可以完全控制类型类解析。但实际问题似乎是维护类型安全而不是类型类解析,因为我将使用类型验证器进行演示。由于代码是类型注释的,它在某种程度上与语言无关。

// please assume `Number` to be a non-polymorphic `Int` type

Sum.Monoid                          // Monoid<Number>
Prod.Monoid                         // Monoid<Number>
Function.Monoid                     // Monoid<b> -> Monoid<(a -> b)>
Function.Monoid(Prod.Monoid)        // Monoid<(a -> Number)>
Function.Monoid(Prod.Monoid).append // (a -> Number) -> (a -> Number) -> a -> Number

当您应用这些类型时,类型安全性会受到影响,因为可以只编写以下人为的表达式而没有验证器抱怨:

Function.Monoid(Prod.Monoid)
  .append(inc) (inc) (Sum.Monoid.empty); // yields 1 ((0 + 1) * (0 + 1)) instead of 4 ((1 + 1) * (1 + 1))

在 Haskell 中,每个实例都有自己独特的类型来防止这种无意义的表达。但是必须从一种类型包装器转换为另一种类型包装器可能很乏味。

是否有可行的、类型安全的替代方案,或者这正是 Haskell 的设计者选择不同类型每类实例简化的原因?

4

2 回答 2

3

也许一种可能性是拥有某种范围的实例机制。有点半生不熟,但想象一下这样写:

with Prod.Monoid, Function.Monoid {
    append(inc)(inc)(empty)
}

(Saywith X, Y, Z {e}是 . 的缩写with X {with Y {with Z {e}}}。)大概在该范围内为给定的类/类型对引入第二个实例是错误的:

with Prod.Monoid, Function.Monoid {
    -- error: conflicting instances for Number
    --     currently in scope: Prod.Monoid
    --     proposed additional instance: Sum.Monoid
    append(inc)(inc)(with Sum.Monoid {empty})
}

但是允许在不同的范围内使用不同的实例:

with Prod.Monoid, Function.Monoid {
    append(inc)(inc)
}(with Sum.Monoid {empty})

尽管您提出的术语仍然可以这样写,但至少它会明确抽象边界的位置。

在存在多态性和高阶函数的情况下使这样的功能正常工作似乎是……令人兴奋的。可能是可发表的研究级令人兴奋的。我很想知道是否可以一直推动集中尝试。

于 2021-07-08T15:49:56.193 回答
2

在某些情况下,您可以安全地使用本地显式实例。在 Haskell 中,该reflection库可用于创建这样的本地实例。例如,参见Reified Monoids

还有一篇关于将其构建到语言中并可能使其更可用和更通用的论文,但我还没有阅读: Thomas Winant 和 Dominique Devriese 的Coherent Explicit Dictionary Application for Haskell,这是他们贡献部分的引述:

在本文中,我们提出了一种新形式的显式字典实例化,它保留了连贯性,并且在实例的全局唯一性方面是安全的,但可以直接应用于常见用例。

于 2021-07-08T15:42:39.830 回答