在PartDefine的定义和应用中都会出现代价高昂的计算

时间:2011-08-18 20:18:37

标签: scala

很有可能要知道某个函数是否在某个时刻定义,计算其价值的重要部分必须完成。在PartialFunction中,在实施isDefinedapply时,两种方法都必须这样做。怎么办这个常见的工作成本很高?

有可能缓存其结果,希望在isDefined之后调用apply。绝对丑陋。

我经常希望PartialFunction[A,B]Function[A, Option[B]],这显然是同构的。或许,PartialFunction中可能有另一种方法,比如applyOption(a: A): Option[B]。对于一些mixins,实现者可以选择实现isDefined和apply或applyOption。或者所有这些都是安全的,性能明智。在呼叫申请之前测试isDefined的客户将被鼓励使用applyOption代替。

但事实并非如此。库中的一些主要方法,其中集合中的collect需要PartialFunction。有没有干净(或不那么干净)的方法来避免支付isDefined和apply之间重复的计算?

此外,applyOption(a: A): Option[B]方法合理吗?在未来的版本中添加它听起来可行吗?它值得吗?

4 个答案:

答案 0 :(得分:4)

为什么要缓存这样的问题?在大多数情况下,您有本地计算,因此只要您为缓存编写包装器,就不必担心它。我的实用程序库中有以下代码:

  class DroppedFunction[-A,+B](f: A => Option[B]) extends PartialFunction[A,B] {
    private[this] var tested = false
    private[this] var arg: A = _
    private[this] var ans: Option[B] = None
    private[this] def cache(a: A) {
      if (!tested || a != arg) {
        tested = true
        arg = a
        ans = f(a)
      }
    }        
    def isDefinedAt(a: A) = {
      cache(a)
      ans.isDefined
    }
    def apply(a: A) = {
      cache(a)
      ans.get
    }
  }
  class DroppableFunction[A,B](f: A => Option[B]) {
    def drop = new DroppedFunction(f)
  }
  implicit def function_is_droppable[A,B](f: A => Option[B]) = new DroppableFunction(f)

然后如果我有一个昂贵的计算,我会编写一个函数方法A => Option[B]并执行类似(f _).drop的操作,以便在collect或whatnot中使用它。 (如果你想内联它,你可以创建一个方法,取A=>Option[B]并返回一个部分函数。)

(相反的变换 - 从PartialFunctionA => Option[B] - 被称为提升,因此“下降”;“unlift”是一个更广泛使用的术语,用于相反的操作。)

答案 1 :(得分:3)

看一下这个帖子Rethinking PartialFunction。你不是唯一一个对此感到疑惑的人。

答案 2 :(得分:2)

这是一个有趣的问题,我会给出2美分。

首先抵制过早优化的冲动。确保部分功能是问题。我对他们在某些情况下的速度感到惊讶。

现在假设有问题,它会从哪里来?

  1. 可能是大量的案例条款
  2. 复杂模式匹配
  3. if cause
  4. 的一些复杂计算

    一种选择我试图找到快速失败的方法。将模式匹配分解为图层,然后链接部分函数。这样你就可以提早失败。还提取重复的子匹配。例如:

    让我们假设OddEvenList是一个将列表分成奇数列表和偶数列表的提取器:

    var pf1: PartialFuntion[List[Int],R] = { 
       case OddEvenList(1::ors, 2::ers) =>
       case OddEvenList(3::ors, 4::ors) => 
    }
    

    分为两部分,一部分与分裂相匹配,然后一部分试图匹配重复结果(以避免重复计算。但这可能需要一些重新设计

    var pf2: PartialFunction[(List[Int],List[Int],R) = {
       case (1 :: ors, 2 :: ers) => R1
       case (3 :: ors, 4 :: ors) => R2
    }
    var pf1: PartialFuntion[List[Int],R] = { 
       case OddEvenList(ors, ers) if(pf2.isDefinedAt(ors,ers) => pf2(ors,ers)
    }
    

    我在逐步阅读难以格式化的XML文件时使用了这个。

    另一个选择是使用andThen撰写部分功能。虽然这里的快速测试结果表明只有第一个实际上是测试。

答案 3 :(得分:0)

部分函数内部的缓存机制绝对没有问题,如果:

  1. 当传递相同的参数时,该函数返回相同的输入
  2. 没有副作用
  3. 它完全隐藏在世界其他地方
  4. 这样的缓存函数不能从普通的纯部分函数中解除...