【问题标题】:Set of functions that are instances in a common way以通用方式作为实例的函数集
【发布时间】:2017-09-07 04:08:43
【问题描述】:

我对 haskell 很陌生,我想我陷入了一些 OO 陷阱。这是我在实现时遇到问题的结构(简化)的草图:

  • 作用于样本列表 (Int) 以产生结果 (Int) 的 Observable 的概念

    • 一个概念 SimpleObservable 使用某种模式来实现结果(同时会有 Observables 以其他方式实现),例如有点像平均水平
  • 一个函数实例,例如一个平均值乘以常数

我的第一个想法是使用子类;类似的东西(下面有点做作,但希望能传达这个想法)

class Observable a where
  estimate :: a -> [Int] -> Int

class (Observable a) => SimpleObservable a where
  compute :: a -> Int -> Int
  simpleEstimate :: a -> [Int] -> Int
  simpleEstimate obs list = sum $ map compute list

data AveConst = AveConst Int

instance Observable AveConst where
  estimate = simpleEstimate

instance SimpleObservable AveConst  where
  compute (AveConst c) x = c * x

但是,即使像上面这样编译它也很丑陋。谷歌搜索告诉我DefaultSignatures 可能会有所帮助,因为我不必为每个实例都做estimate = simpleEstimate,但从围绕它的讨论来看,这样做似乎是一种反模式。

另一种选择是没有子类,但类似(具有相同的Observable 类):

data AveConst = AveConst Int

instance Observable AveConst where
  estimate (AveConst c) list = sum $ map (*c) list

但是这样我不确定如何重用该模式;每个Observable 都必须包含完整的estimate 定义,并且会有代码重复。

第三种方式是带有函数字段的类型:

data SimpleObservable = SimpleObservable {
  compute :: [Int] -> Int
} 

instance Observable SimpleObservable where
  estimate obs list =
    sum $ map (compute obs) list

aveConst :: Int -> SimpleObservable
aveConst c = SimpleObservable {
  compute = (*c)
}

但我也不确定这是否是惯用的。有什么建议吗?

【问题讨论】:

  • "即使上面编译,也很丑。"我有个坏消息:它很丑,并且它不能编译。
  • 也许您应该解释您要解决的问题是什么。作为一个非常粗略的直觉,将 Haskell 类型类视为可用于实现 OOP 函数重载的东西。你需要那个来解决你的问题吗?作为一般的经验法则,如果你只有一个实例,这就像有一个重载,所以你实际上不需要类型类。

标签: class haskell default subclassing repeat


【解决方案1】:

我建议更简单:

type Observable = [Int] -> Int

那么,平均 observable 是:

average :: Observable
average ns = sum ns `div` length ns

如果你的Observable 需要一些数据——比如说,要乘以一个常数——没问题;这就是闭包的用途。例如:

sumTimesConst :: Int -> Observable
sumTimesConst c = sum . map (c*)

您可以毫无困难地抽象出Observables 的构造;例如如果你想要一个只查看元素然后求和的SimpleObservable,你可以:

type SimpleObservable = Int -> Int

timesConst :: Int -> SimpleObservable
timesConst = (*)

liftSimple :: SimpleObservable -> Observable
liftSimple f = sum . map f

那么liftSimple . timesConst 是另一种完美的拼写sumTimesConst 的方式。

...但老实说,做上述任何事情我都会觉得自己很脏。 sum . map (c*) 是一个完全可读的表达式,没有为其类型引入一个可疑的新名称。

【讨论】:

  • 好点!但最后一段是正确的,因为这是实际问题的简化草图。
  • 我想我唯一的问题是:如果我错误地将具有正确签名但从未打算用作可观察的函数用作可观察的函数,编译器将不会知道。或者这不是一个好的思考方式?
  • @jorgen 这是一种很好的思考方式。在这种情况下,您可以使用newtype 包装器来区分Observable[Int] -> Int;但是锻炼品味——newtypes 太多可能和太少一样糟糕。
【解决方案2】:

我还没有完全理解这个问题,但我会在了解更多信息时编辑这个答案。

作用于列表并产生结果的东西可以简单地是一个函数。该函数的接口(即类型)可以是[a] -> b。这表示该函数接受某种类型的元素列表并返回可能不同类型的结果。

现在,让我们发明一个小问题作为例子。我想获取一个列表列表,列表上的某个函数会产生一个数字,将此函数应用于每个列表,然后返回数字的平均值。

average :: (Fractional b) => ([a] -> b) -> [[a]] -> b
average f xs = sum (fmap f xs) / genericLength xs

例如,average genericLength 会告诉我子列表的平均长度。我不需要定义任何类型类或新类型。简单地说,我将函数类型 [a] -> b 用于那些将列表映射到某个结果的函数。

【讨论】:

  • 谢谢!但问题的症结(可能在 OP 中没有正确表达)是会有几个函数,SimpleObservables,(其中 OP 中的aveConst 是一个例子)与Observables 相似但不同的方式,我想避免这些之间的代码重复。在某种程度上,“外部”功能(示例中的平均值)将是常见的,但“内部”不会(示例中为常数的乘积)。此外,重要的是,还有其他类型的 Observables 具有不同的“外部”结构,即不是 SimpleObservables。
  • 也许在尝试实现复杂的抽象之前花更多的时间在 Haskell 上,jorgen。我很难理解你想要达到的目标。许多函数都有[a] -> b 类型,用于ab 的各种选择。如果我们选择bFractional 类型,genericLength 就是这样一个函数。
  • 在我看来这不是很复杂,erisco。如前所述,OP 是实际问题的简化草图,这就是为什么它有点做作,并且正如您所指出的那样似乎有一个更简单的解决方案。
  • 您通常不需要定义新的类型类。虽然在 OOP 中很常见,但在 FP 中并不常见。我的意思是,如果你花更多时间学习 Haskell 的基础知识,你可能会更清楚地了解如何实现你的想法。我不确定 Observable 应该是什么,除非它像 C# 中的那样,在这种情况下,已经有库可以根据您的具体需要为您提供帮助。
  • 嗯,我想我认为Observable 类是一种组织原则;每次我想编写一个 observable 时,编译器都会强制它具有正确类型的函数。当然,如果我尝试在“可观察上下文”中使用具有错误签名的任何函数并发出不同的警告,它也会抱怨。你可能是对的,也许一个新的类不是答案,也不会提供更多的类型安全性。
猜你喜欢
  • 2011-08-09
  • 2012-10-16
  • 2021-02-14
  • 1970-01-01
  • 2015-06-30
  • 2019-05-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多