是否有可行且类型安全的替代方法,以替代 1:1 类型/类型类实例关系?

Are there viable and type safe alternatives to the 1:1 type/type-class-instance relation?

提问人: 提问时间:7/8/2021 最后编辑:David Young 更新时间:7/9/2021 访问量:176

问:

这个问题最初是在我研究动态 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 的设计者选择每个类实例的不同类型简化的原因?

Haskell 函数式编程 语言与类型 无关 adhoc-多态性

评论

0赞 user2864740 7/8/2021
这难道不类似于一般亚型的基本“问题”吗?必须相信子类型(或任何可转换类型,实际上)的行为符合合理的合同预期。
2赞 Daniel Wagner 7/8/2021
我很困惑。为什么这会违反类型安全?当然,在最坏的情况下,它的行为只是与连贯的类型类解析机制不同,但不会产生任何实际的类型不匹配。...为什么这是一个问题?这不正是你想要的吗?或者,如果您不想在代码的不同部分使用不同的实例,为什么还要提供这种非 1:1 功能?
1赞 Li-yao Xia 7/8/2021
这有什么不安全的类型?您可以在不转换的情况下一起使用吗?你也可以用不同的类型包装,这并不是 Haskell 独有的。SumProdNumber
3赞 K. A. Buhr 7/8/2021
爱德华·克梅特(Edward Kmett)有一个您可能会觉得有趣的视频。它讨论了“一致性”(类型和类型类实例之间的 1:1 关系)的优点。您已经确定了一致性是真正可取属性的众多情况之一。
0赞 daylily 7/9/2021
您可以使用一些字符串标记(在 Haskell 中,在 TS 中为字符串文字类型)来区分实例。Symbol

答:

1赞 Noughtmare 7/8/2021 #1

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

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

在本文中,我们提出了一种新的显式词典形式 实例化,保持连贯性,并且安全 尊重实例的全局唯一性,但可以直接 适用于常见用例。

评论

1赞 dfeuer 7/9/2021
这篇论文不是很令人满意,IMO。它的相干机制对我来说闻起来很脆。我真的不相信它,我认为他们会发现在面对其他语言扩展时很难维护。我提议通过本地类型声明的方式向语言添加反射,但被拒绝了。
0赞 Jon Purdy 7/9/2021
像透镜这样的光学器件也与局部显式实例非常相似:a的行为有点像显式字典,a像字典,等等。光学器件指定了您希望如何访问和/或聚合,作为“真实”实例的组合,从一些常见的基元(如 、 和 )开始。但是没有相干性的要求,光学库也倾向于以合法性“作弊”。所以 imo 有一些更有原则的东西试图摆脱。FoldFoldableTraversalTraversableMonoidFunctorApplicative
2赞 Daniel Wagner 7/8/2021 #2

也许一种可能性是具有某种作用域实例机制。有点半生不熟,但想象一下写这样的东西:

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

(Say 是 的缩写。据推测,在该范围内为给定的类/类型对引入第二个实例将是一个错误:with 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})

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

使这样的特征在存在多态性和高阶函数的情况下正常工作似乎是......令人兴奋的。可能是可发表的研究水平令人兴奋。我很想知道是否可以一直推动一个专注的尝试。

评论

0赞 7/9/2021
这就是我所希望的那种想法/草图。谢谢。我会试着仔细考虑,甚至可能实施它,前提是我能走到这一步。
1赞 Ben 7/9/2021
@IvenMarquardt我认为,在这种设计中,你需要更多地将字典存储在数据结构中,或者使用某种类似的技巧来防止事情“泄漏”到它们有效的范围之外。想想 Haskell 的 .基本上,所有用于操作映射的函数都对键类型有约束,但是在一个字典下构造映射,然后使用不同的字典从中检索项目是没有意义的。但是,如果映射可以转义范围,则可能会发生这种情况,其中实例的唯一性仅针对每个范围。STData.MapOrdOrdOrd
0赞 Ben 7/9/2021
@IvenMarquardt 在这样的类型类系统中,API 确实需要从根本上不同。您需要在创建映射时捕获和存储字典,而不是对所有函数进行约束。但是,您如何处理此类操作?这确实需要保证两个映射使用相同的实例,以实现高效实现。Data.MapOrd kmerge