【问题标题】:Haskell counterpart of list of abstract class objects in OOPOOP中抽象类对象列表的Haskell对应物
【发布时间】:2018-08-11 08:06:55
【问题描述】:

我正在学习 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))

我的一个想法是,我们不使用类型类,而是简单地将 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 State1Metric Int State1)。一个想法是创建 data MetricState = ... | ... | ... 并将 state :: MetricState 设置为 Metric 的字段,这样每个特定指标都可以定义自己的状态类型。

【问题讨论】:

  • 在 Java 中什么是具有单一方法的对象,在 Haskell 中只是一个函数。
  • @n.m.它可能携带的状态如何?具有多个方法的对象呢?
  • Haskell 有部分功能应用,例如f = (+) 5 定义了一个函数 f,其中一个参数带有一个内部状态(单个值 5)。当应用于参数时,比如f x,它返回5 + x。 ((+)两个 参数的函数)。
  • 至于几种方法,表示这样一个对象最简单的方法就是函数的记录。

标签: haskell functional-programming


【解决方案1】:

您的 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

其他一切都一样。

【讨论】:

  • 此外,闭包和对象都已经是存在类型的示例,只是融入了语言。使用闭包,您将隐藏捕获的值(如Button)并呈现具有类似MetricQuery -> Int 的函数类型的接口;对于一个对象,您隐藏了对象的字段(和具体类型)并呈现一个具有一些方法签名的接口,例如computeValue(q: MetricQuery): Int。也就是说,闭包与具有一种方法的对象同构,因此您可以使用闭包非常直接地表示一种方法的对象,如下所示。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-09-24
  • 2014-02-05
  • 1970-01-01
  • 2015-12-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多