在更加函数式的方式(使用scalaz)中使用Scala会导致性能/可维护性下降吗?

16

我目前正在从事一个小项目(< 10k loc),主要是纯编程,但是依赖于基于迭代器的可变优化和一些数据结构重用来进行重型计算。

我想学习更多函数式编程,并通过将可变计算包装成状态变换单子等方式获得更多类型安全性。为此存在scalaz库。

问题一

当使用所有花哨的功能性东西在更大范围上抽象我的计算时,是否会引入无法摆脱的性能杀手?例如,当我的计算在Monad中深深地包裹时?

问题二

考虑到Scala的有限类型推断,这是否可行?我目前正在与非常大的类型签名作斗争(可能是因为我不知道如何正确摆脱它们)。我认为,走更“函数式”的路线将引入更多类似的样板代码。

免责声明

我不怀疑函数式方法是好还是坏。对于Haskell提出这个问题毫无意义。我质疑是否在Scala中这样做是明智的。

按请求编辑:我项目中的大型类型签名示例

(但这将是另一个问题)

下面的代码描述了对类型参数化输入对象(DiscreteFactorGraph [VariableType,FactorType [VariableType]] )进行迭代计算。您可以使用createInitialState 构造计算对象,并在advanceState 上执行计算,最后使用marginals 从中提取一些信息。

我希望保留因子图对象的类型(及其参数类型)在计算过程中,以便最终应用marginals 会产生正确类型的 DiscreteMarginals [VariableType] 。我认为目前只需要在计算类型(即 TState )中保留变量类型,因此不需要使用因子类型。但是在其他地方,我甚至需要DiscreteFactorGraph 的类型是可变的,因此我倾向于在将来需要通过计算传递更多类型信息。

我一直在琢磨这个部分,希望有更好的解决方案。目前我采用的是相当功能性的方法,只有这三个函数。但我必须通过它们链接类型。或者我可以将其定义为类并将所有这些类型参数化,以便在每种方法中不必重复类型参数。

object FloodingBeliefPropagationStepper extends SteppingGraphInferer {
  def marginals[V <: DiscreteVariable, F <: DiscreteFactor[V]](state: FloodingBeliefPropagationStepper.TState[V,F]): DiscreteMarginals[V] =
    BeliefPropagation.marginals(state._1, state._2)

  def advanceState[V <: DiscreteVariable, F <: DiscreteFactor[V]](state: FloodingBeliefPropagationStepper.TState[V,F]): FloodingBeliefPropagationStepper.TState[V,F] = {
    val graph = state._1
    (graph,
      BeliefPropagation.computeFactorMessages(
      graph,
      BeliefPropagation.computeVariableMessages(graph, state._2, graph.variables),
      graph.factors))
  }

  def createInitialState[V <: DiscreteVariable, F <: DiscreteFactor[V]](graph: DiscreteFactorGraph[V, F],
                                                                        query: Set[V],
                                                                        random: Random): FloodingBeliefPropagationStepper.TState[V,F] = {
    (graph,
      BeliefPropagation.computeFactorMessages(
      graph,
      BeliefPropagation.createInitialVariableMessages(graph, random),
      graph.factors))
  }

  type TState[V <: DiscreteVariable, F <: DiscreteFactor[V]] = (DiscreteFactorGraph[V,F],Map[(F,V),DiscreteMessage])
}

1
你能给出一两个你的大型类型签名的例子吗? - CheatEx
你的类型签名之所以难以管理,是因为你选择了像FloodingBeliefPropagationStepper.TState这样非常长的名称。如果你想要高性能的解决方案,使用DiscreteVariable而不是Int或类似的东西可能会导致最大的性能损失(除非你实际上对变量做很少的操作)。到目前为止,代码看起来是为了最大化抽象而不是性能而编写的。那么你真的应该担心性能吗? - Rex Kerr
@Rex DiscreteVariable只是变量本身的描述,并不持有任何状态(用于保存计算状态的具体数据结构是根据由图表描述的“问题说明”在较低级别上创建的)。实际上,您正在查看抽象类,而计算位于较低级别,例如BeliefPropagation - ziggystar
2
作为一个写大量高性能代码的人,我可以向你保证,如果你没有仔细地将数据打包成原始类型并在可变状态下进行计算,那么你的代码在大多数情况下会表现得比C++差。Scalaz也不例外。然而,我还不能评论Scalaz与非Scalaz函数式不可变风格之间的相对性能,这就是为什么这只是一条评论而不是答案的原因。 - Rex Kerr
就可维护性而言,根据我的经验,以“scalaz方式”做事可以略微提高可维护性,但是必须完全按照“scalaz方式”去做事的理念对于可维护性来说是一场灾难。 - Owen
显示剩余2条评论
1个回答

5

关于问题一:

将计算封装成单子、应用、函数式魔法等会增加一些开销。但是,将计算封装成过程、方法、对象也会有同样的开销。

问题的核心是,计算有多小,才能开始注意到封装的影响。如果不了解您项目的细节,没有人能告诉您具体情况。然而,由于Scala的混合特性,您不必一路采用单子模式。在计算高级组合时,可以使用类似于scalaz的风格,并在性能要求时使用局部可变状态。

关于问题二:

由于我不了解您的类型签名的性质,因此无法确定scalaz是否通过泛化计算来帮助您,或者您是否需要围绕Scala对部分类型构造器应用的有限支持进行类型推导。

如果您的类型签名变得太复杂,请尝试在包对象中声明类型别名并使用它们。


我也在考虑只将功能性的东西应用到较高层面,但不确定是否可行。谢谢你让我放心。 - ziggystar

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接