gpt4 book ai didi

haskell - 何时使用类型类,何时使用类型

转载 作者:行者123 更新时间:2023-12-03 07:51:27 25 4
gpt4 key购买 nike

我正在重新审视几个月前我编写的一段代码来进行组合搜索,并注意到有一种替代的、更简单的方法来完成我以前用类型类实现的事情。

具体来说,我之前有一个用于搜索问题类型的类型类,它的状态类型为 s , a 类型的操作(对状态的操作) ,初始状态,获取 (action,state) 对列表的方法以及测试状态是否为解决方案的方法:

class Problem p s a where
initial :: p s a -> s
successor :: p s a -> s -> [(a,s)]
goaltest :: p s a -> s -> Bool

这有点不令人满意,因为它需要 MultiParameterTypeClass 扩展,并且当您想要创建此类的实例时,通常需要 FlexibleInstances 和可能的 TypeSynonymInstances。它还会使您的函数签名变得困惑,例如
pathToSolution :: Problem p => p s a -> [(a,s)]

我今天注意到我可以完全摆脱这个类,并使用一个类型来代替,如下所示
data Problem s a {
initial :: s,
successor :: s -> [(a,s)],
goaltest :: s -> Bool
}

这不需要任何扩展,函数签名看起来更好:
pathToSolution :: Problem s a -> [(a,s)]

而且,最重要的是,我发现在重构我的代码以使用这个抽象而不是类型类之后,我剩下的行数比以前少了 15-20%。

最大的胜利是使用类型类创建抽象的代码 - 以前我必须创建新的数据结构,以复杂的方式包装旧的数据结构,然后将它们变成 Problem 的实例。类(需要更多的语言扩展) - 很多行代码来做一些相对简单的事情。重构之后,我只有几个功能完全符合我的要求。

我现在正在查看其余的代码,试图找出可以用类型替换类型类的实例,并取得更多胜利。

我的问题是:在什么情况下会进行重构 不是 工作?在什么情况下使用类型类而不是数据类型实际上更好,您如何提前识别这些情况,以便您不必经历代价高昂的重构?

最佳答案

考虑类型和类都存在于同一个程序中的情况。类型可以是类的一个实例,但这很简单。更有趣的是你可以写一个函数fromProblemClass :: (CProblem p s a) => p s a -> TProblem s a .

您执行的重构大致相当于手动内联 fromProblemClass在任何地方构建用作 CProblem 的东西实例,并制作每个接受 CProblem 的函数实例改为接受 TProblem .

由于这次重构唯一有趣的部分是 TProblem 的定义。和实现 fromProblemClass ,如果您可以为任何其他类编写类似的类型和函数,您同样可以重构它以完全消除该类。

这什么时候工作?

想想fromProblemClass的实现.您基本上将部分地将类的每个函数应用于实例类型的值,并在此过程中消除对 p 的任何引用。参数(这是类型替换的内容)。

任何直接重构类型类的情况都将遵循类似的模式。

这什么时候会适得其反?

想象一个简化版的 Show ,只有 show功能定义。这允许相同的重构,应用 show并用...替换每个实例 String .很明显,我们在这里失去了一些东西——即,使用原始类型并将它们转换为 String 的能力。在各个点。 Show的值是它定义在各种不相关的类型上。

根据经验,如果作为类的实例的类型有许多不同的函数,并且这些函数通常与类函数在同一代码中使用,那么延迟转换是有用的。如果单独处理类型的代码和使用该类的代码之间存在明显的分界线,则转换函数可能更适合类型类,因为类型类具有较小的语法便利。如果类型几乎完全通过类函数使用,则类型类可能完全是多余的。

这什么时候不可能了?

顺便说一下,这里的重构类似于OO语言中类和接口(interface)的区别;类似地,无法进行这种重构的类型类是那些在许多面向对象语言中根本无法直接表达的类型。

更重要的是,一些您无法以这种方式轻松翻译的示例(如果有的话):

  • 类的类型参数仅出现在协变位置 ,例如函数的结果类型或非函数值。这里值得注意的罪犯是 memptyMonoidreturnMonad .
  • 类的类型参数在函数类型中多次出现 可能不会真正使这成为不可能,但它使事情变得非常复杂。这里值得注意的罪犯包括 Eq , Ord ,基本上每个数字类。
  • 较高种类的非平凡使用 ,我不确定如何确定具体细节,但 (>>=)Monad这里是一个值得注意的罪犯。另一方面,p类中的参数不是问题。
  • 多参数类型类的非平凡使用 ,我也不确定如何确定并且在实践中变得非常复杂,与 OO 语言中的多次分派(dispatch)相当。同样,您的类(class)在这里没有问题。

  • 请注意,鉴于上述情况,对于许多标准类型类甚至无法进行这种重构,并且对于少数异常(exception)情况会适得其反。这并非巧合。 :]

    通过应用这种重构,你放弃了什么?

    您放弃了区分原始类型的能力。这听起来很明显,但它可能很重要——如果在任何情况下您确实需要控制使用哪个原始类实例类型,则应用此重构会失去某种程度的类型安全性,您只能通过跳过其他地方使用的相同类型的箍以确保运行时的不变性。

    相反,如果在某些情况下您确实需要使各种实例类型可互换——您提到的令人费解的包装是这种情况的典型症状——您可以通过丢弃原始类型获得很多好处。在这种情况下,您实际上并不关心原始数据本身,而是关心它如何让您对其他数据进行操作;因此,直接使用函数记录比额外的间接层更自然。

    如上所述,这与 OOP 及其最适合的问题类型密切相关,并且从 ML 风格的语言中的典型代表表达问题的“另一面”。

    关于haskell - 何时使用类型类,何时使用类型,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/12286315/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com