何时以及为何解除功能

When and why to Lift a Function

考虑这段代码:

def f1(a: Int, b: Int) = a + b

def f2(a: Option[Int], b: Option[Int]): Int = (a, b) match {
  case (Some(x), Some(y)) => x + y
  case _ => throw new IllegalArgumentException
}

println(f1(10, 20))
println(f2(Some(10), Some(20)))

我听说您可以“提升”f1 使其像 f2。作为初学者,我有以下问题:

什么是提升,为什么要使用?在实现方面,如何“提升”f1?

非常感谢任何解释,因为很难找到我为什么要“举起”某物的原因

原因:当您有一个带有 f1 签名的函数并想在 Option[Int]s(或 List[Int]s 等)上“调用它”时

方法:可以直接写:

def lift2option[A, B, C](f: (A, B) => C): (Option[A], Option[B]) => Option[C] = ???

我将其保留为未定义状态,因为您应该尝试自己编写它;您对 f2 的定义应该是一个很好的起点。请注意,我将其设为 return Option[Int] 而不是 Int。如果你愿意,我稍后可以编辑并给出答案。

然后不是将 f2 定义为单独的函数,而是:

val f2 = lift2option(f1 _)
println(f2(Some(10), Some(20)))

当然,关键是现在对于 任何 带有像 f1 这样的签名的函数,你可以获得 f2 等价物。

它可以进一步推广,不仅适用于 Option,但您稍后会想研究它。

我只是想补充一点,提升一个函数可以用作 functor 上映射的替代方法。例如,如果您有 2 个 Option[Int] 对象,您想要应用 f1,您可以这样做:

val sum: Option[Int] = option1.flatMap { x => option2.map{ y => x + y } }

请注意,结果是 Option[Int]。正如 Alexey Romanov 所说,f2 的 return 类型也应该是 OptionOption 的全部要点是让您对值进行操作而不用担心 NullPointerException 或其他错误,因为该值不存在。

然而,这个映射有点冗长,而且必须决​​定何时需要使用 flatMapmap 很烦人。这就是提升派上用场的地方。

让我们定义 f2 更好地处理 Nones:

def f2(a: Option[Int], b: Option[Int]): Option[Int] =
  a match {
    case Some(x) => b match {
      case Some(y) => Some(x + y)
      case None => None
    }
    case None => None
  }

我们也可以根据 f1 来定义它,将 x + y 替换为 f1(x + y)

def f2(a: Option[Int], b: Option[Int]): Option[Int] =
  a match {
    case Some(x) => b match {
      case Some(y) => Some(f1(x, y))
      case None => None
    }
    case None => None
  }

现在,f2 不需要知道如何添加数字,它只需要使用 f1 来添加它们。实际上,我们甚至可以将 f1 作为 f2 的参数。

def f2(f1: (Int, Int) => Int)(a: Option[Int], b: Option[Int]): Option[Int] =
  a match {
    case Some(x) => b match {
      case Some(y) => Some(f1(x, y))
      case None => None
    }
    case None => None
  }

看到那里发生了什么吗?我们刚刚使用 f2f1(Int, Int) => Int“提升”到 (Option[Int], Option[Int]) => Option[Int]。实际上,我们将其重命名为 lift2。我们还可以让它更通用:

def lift2[A, B, C](f1: (A, B) => C)(a: Option[A], b: Option[B]): Option[C] =
  a match {
    case Some(x) => b match {
      case Some(y) => Some(f1(x, y))
      case None => None
    }
    case None => None
  }

lift2 现在是一个接受类型函数 (A, B) => C 的函数(这里,ABC 都是 Int for f1) 和 return 类型 (Option[A], Option[B]) => Option[C] 的另一个函数。现在,我们不必使用那些笨拙的嵌套 maps 和 flatMaps。你可以这样做:

val sum: Option[Int] = lift2(f1)(option1, option2)

你当然也可以定义lift3lift4等,但是只定义一个lift1函数并使用currying来做可能更容易剩下的

当然,如果您知道如何拆分和组合要提升到的类型,则只能 lift 一个函数。例如,如果 Some 是一个具有私有 unapply 方法的对象,并且不可能对其进行模式匹配,那么您将无法提升 f1。如果 Some 的构造函数是私有的并且您不可能创建新的 Options.

,也会发生同样的情况

编辑:以下是您可以将多个 Option[Int] 对象与 f1lift2 函数一起添加的方法。

val f2 = lift2(f1)
val optionSum = f2(f2(option1, option2), option3)

没有 f2,它看起来像这样

val sum1 = option1 match {
  case Some(x) => option2 match {
    case Some(y) => Some(f1(x, y))
    case None => None
  }
  case None => None
}
val finalSum = sum1 match {
  case Some(x) => option3 match {
    case Some(y) => Some(f1(x, y))
    case None => None
  }
  case None => None
}