Инверсия метода лифта PartialFunction - PullRequest
22 голосов
/ 05 мая 2011
Метод

PartialFunction lift превращает функцию PartialFunction в Function, возвращая результат Option.

Есть ли обратная операция, которая превращает Function1[A, Option[B]] в PartialFunction[A, B]?

Ответы [ 6 ]

39 голосов
/ 07 мая 2011

Трудно превзойти все эти прекрасные ответы из ряда светильников 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
}
6 голосов
/ 05 мая 2011

Чтобы построить ответ Джеймса на более сложном примере, в моей библиотеке вещей-the-Scala-library-забыли (или не доверяли вам) следующий код:

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)

Большая часть кода посвящена обеспечению того, чтобы оценка функции кэшировалась (до тех пор, пока apply применяется сразу после 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).

6 голосов
/ 05 мая 2011

Не в библиотеке, но легко построить. Однако 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....

Пурист также может обернуть isDefinedAt блоком try / catch для возврата false при исключениях.

1 голос
/ 23 февраля 2017

Вы можете использовать Function.unlift, См. Документы .

1 голос
/ 05 мая 2011

Чтобы построить ответ Джеймса ...

Также возможно кешировать результат, сгенерированный в isDefinedAt, а затем вернуть его при вызове в apply, что позволяет избежать двойного выполнения.

Тем не менее, Scala не применяет чистые функции, и поэтому легко найти примеры из реальной жизни, в которых любая стратегия неприсоединения даст неожиданные и неожиданные результаты. Из-за этого обычно не рекомендуется «поднимать» что-либо в PartialFunction

0 голосов
/ 09 июля 2015

Мы всегда должны использовать литерал частичной функции для построения PartialFunction, потому что это слишком тривиально, чтобы правильно реализовать applyOrElse для PartialFunction.

Таким образом, правильное 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 }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...