OOP中抽象类对象列表的Haskell对应物



我正在学习Haskell,遇到了这个问题。在不使用存在类型的情况下,我们如何将以下 OOP 伪代码转换为 Haskell?不是在Haskell中简化OOP概念的方法,而是正确的Haskell方法。

class MetricQuery { ... }
abstract class Metric[T] {
def computeValue(q: MetricQuery): T
}
class LinkClicksMetric extends Metric[Int] { ... }
class ButtonClicksMetric extends Metric[Int] { ... }
class PostCommentsMetric extends Metric[Int] { ... }
...
query: MetricQuery = ...
metrics: List[Metric[Int]] = ...
results: List[Int] = metrics.map(x -> x.computeValue(query))

我的一个想法是,我们不是使用typeclass,而是简单地将computeValue函数作为Haskell数据类型的字段:

data MetricQuery = ...
data Metric a = Metric {
computeValue :: MetricQuery -> a
}
linkClicksMetric :: Metric Int
linkClicksMetric = Metric { computeValue = q -> ... }
buttonClicksMetric :: Metric Int
buttonClicksMetric = Metric { computeValue = q -> ... }
results =
let query = ...
metrics = ...
in fmap (x -> computeValue x query) metrics

我认为这可以工作,只是不确定这是否是一种"正确"的 Haskell 方法。

另外,我不知道如何使用特定指标存储其他数据(即 OO 术语中的成员变量(。我试图使用状态类型参数化指标类型构造函数,但这会导致不同的特定指标类型(例如Metric Int State1vs.Metric Int State1(。一个想法是创建一个data MetricState = ... | ... | ...,并使state :: MetricState成为Metric字段,以便每个特定指标都可以定义自己的状态类型。

您的Metric数据类型可能更适合作为类型同义词。

Metric a = MetricQuery -> a

如果您的特定指标包含其他信息,那么您可以通过部分应用程序获得此信息的一种方式:

buttonClicksMetric :: Button -> Metric Int
buttonClicksMetric button query = ....

这是有效的,因为通过替换类型同义词,您可以将类型读作

buttonClicksMetric :: Button -> MetricQuery -> Int

因此,现在您可以通过将Button传递给buttonClicksMetric来创建Metric Int。同样,您可以使用linkClicksMetric创建另一个Metric Int,并将它们都放在一个列表中。

但是,如果您想使用需要访问按钮的buttonClicksMetric执行其他操作,例如显示它,该怎么办。然后你需要一个数据类型(这种设计模式通常称为函数的"化":

newtype ButtonClicksMetric = ButtonClicksMetric {getButton :: Button}

(旁白:newtype的工作方式几乎与data完全相同,除了您只能拥有一个字段和一个构造函数,并且它的运行时成本为零。底部值的语义存在一些细微差异,您在这里无需担心。如果您在 ButtonClicksMetric 中需要更多字段,则可以使用data.(

buttonClicksMetric的类型现在变成了

buttonClicksMetric :: ButtonClicksMetric -> Metric Int

其他一切都是一样的。

最新更新