【问题标题】:Inverse of PartialFunction's lift methodPartialFunction 的提升方法的逆
【发布时间】:2011-05-05 18:09:33
【问题描述】:

PartialFunctionlift 方法将 PartialFunction 转换为 Function 返回 Option 结果。

是否有与之相反的操作,将Function1[A, Option[B]] 变成PartialFunction[A, B]

【问题讨论】:

标签: scala


【解决方案1】:

很难从一系列 scala 名人中获得所有这些好的答案,但如果您想了解标准库中的答案,它位于 scala.Function 伴侣对象中。 (在 2.9 中。)

/** Turns a function `A => Option[B]` into a `PartialFunction[A, B]`.  Important note:
 *  this transformation implies the original function will be called 2 or more
 *  times on each logical invocation, because the only way to supply an implementation
 *  of isDefinedAt is to call the function and examine the return value.
 *
 *  @param   f    a function T => Option[R]
 *  @return       a partial function defined for those inputs where
 *                f returns Some(_) and undefined where f returns None.
 *  @see PartialFunction#lift
 */
def unlift[T, R](f: T => Option[R]): PartialFunction[T, R] = new PartialFunction[T, R] {
  def apply(x: T): R = f(x).get
  def isDefinedAt(x: T): Boolean = f(x).isDefined
  override def lift: T => Option[R] = f
}

【讨论】:

【解决方案2】:

不在库中,但很容易构建。但是,isDefinedAt 必须对函数进行全面评估,这使得它比通过模式匹配构建的部分函数的典型成本更高,并且还可能导致不需要的副作用。

scala> def unlift[A, B](f : (A => Option[B])) = new PartialFunction[A,B] {
     |    def isDefinedAt(x : A) = f(x).isDefined
     |    def apply(x : A) = f(x).get
     | }
unlift: [A,B](f: (A) => Option[B])java.lang.Object with PartialFunction[A,B]
scala> def f(x : Int) = if (x == 1) Some(1) else None
f: (x: Int)Option[Int]
scala> val g = unlift(f)
g: java.lang.Object with PartialFunction[Int,Int] = <function1>
scala> g.isDefinedAt(1)
res0: Boolean = true
scala> g.isDefinedAt(2)
res1: Boolean = false
scala> g(1)
res2: Int = 1
scala> g(2)
java.util.NoSuchElementException: None.get
    at scala.None$.get(Option.scala:262)
    at scala.None$.get(Option.scala:260)
    at $anon$1.apply(<console>:7)
    at scala.Function1$class.apply$mcII$sp(Function1.scala:39)
    at $anon$1.apply$mcII$sp(<console>:5)
    at .<init>(<console>:9)
    at .<clinit>(<console>)
    at RequestResult$.<init>(<console>:9)
    at RequestResult$.<clinit>(<console>)
    at RequestResult$scala_repl_result(<console>)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
    at java.lang.reflect.Method.invoke(Unknown Source)
    at scala.tools.nsc.Interpreter$Request$$anonfun$loadAndRun$1$$anonfun$apply$17.apply(Interpreter.scala:988)
    at scala.tools....

纯粹主义者也可以用 try/catch 块包装 isDefinedAt 以在异常时返回 false。

【讨论】:

  • 谢谢,双重评估的需要解释了为什么图书馆缺少它。
【解决方案3】:

为了以 James 的回答为基础,举一个更复杂的例子,我的 things-the-Scala-library-forgot(或不信任你)库中有以下代码:

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)

大部分代码都致力于确保函数评估被缓存(只要应用紧跟在 isDefinedAt 之后)。使用示例:

scala> val f = (x: Int) => if (x>=0) Some(x) else None
f: (Int) => Option[Int] = <function1>

scala> Array(-2,-1,0,1,2).collect(f.drop)
res0: Array[Int] = Array(0, 1, 2)

缓存有助于加快处理速度并避免双重副作用问题(至少在 isDefinedAt 紧接在 apply 之前使用时,以及当函数在返回 None 时省略副作用时)。

【讨论】:

  • 哇,这看起来很吓人。它保证不是线程安全的。它可能有它的用途,但是……小心。
  • @JonaChristopherSahnwaldt - 确实。非常不安全,就像大多数使用缓存的东西一样。
  • 我喜欢这个答案。我看到了线程安全的两种潜在解决方案,请查看:一种是将testedargans 包装在一个元组或案例类中,然后DroppedFunction 将始终具有内部一致的状态。另一个是完全避免这个问题,并确保在任何多线程上下文中,DroppedFunction 是在范围本地新创建的。
  • @samthebest - 使用案例类可提供一致性,但可能不会阻止同一功能的多次应用。在synchronized 块中包装isDefinedAtapply 稍微好一些,但它不会阻止用户在一个线程中调用isDefinedAt,然后在另一个线程中调用apply,然后在第一个线程中使用apply并从第二个获得结果.. 最好不要在多线程上下文中使用它,因为您无法确保人们会使用applyOrElse(这可能是安全的)。
【解决方案4】:

您可以使用Function.unliftSee docs

【讨论】:

    【解决方案5】:

    以詹姆斯的回答为基础...

    还可以缓存isDefinedAt 中生成的结果,然后在调用apply 时将其返回,从而避免重复执行。

    但是,Scala 不强制执行纯函数,因此很容易找到现实生活中的示例,其中任何一种简单的策略都会产生令人惊讶和意想不到的结果。因此,通常不建议将某些内容“取消”到PartialFunction

    【讨论】:

      【解决方案6】:

      我们应该始终使用偏函数字面量来构建 PartialFunction,因为对于 PartialFunction 正确实现 applyOrElse 太简单了。

      因此,正确的unlift 应该这样实现:

      // Use AnyVal to avoid the indirect reference to f
      class Extractor[A, B](val f: A => Option[B]) extends AnyVal {
        def unapply(a: A) = f(a)
      }
      
      def unlift[A, B](f: A => Option[B]): PartialFunction[A, B] = {
        val LocalExtractor = new Extractor(f)
      
        // Create the PartialFunction from a partial function literal
        { case LocalExtractor(b) => b }
      }
      

      【讨论】:

      • 有趣。这是否避免了在当前(29-2.11)库实现中看到的双重执行?如果是这样,为什么库实现没有以这种方式实现它?
      猜你喜欢
      • 1970-01-01
      • 2022-01-21
      • 1970-01-01
      • 2020-09-10
      • 2011-07-08
      • 2011-01-11
      • 1970-01-01
      • 1970-01-01
      • 2019-08-07
      相关资源
      最近更新 更多