1

考虑这段代码:

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?

非常感谢任何解释,因为找到我为什么要“举起”某些东西有点棘手

4

2 回答 2

3

为什么:当你有一个带有类似签名的函数f1并想在Option[Int]s(或List[Int]s 等)上“调用它”时

如何:你可以直接写它:

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

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

然后,您可以执行以下操作,而不是定义f2为单独的函数:

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

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

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

于 2020-06-24T12:33:51.280 回答
2

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

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

请注意,结果是Option[Int]. 正如 Alexey Romanov 所说,返回类型f2也应该是Option. 的全部意义Option在于让您对值进行操作而不必担心NullPointerExceptions 或其他错误,因为该值不存在。

但是,这种映射有点冗长,而且必须决​​定何时需要使用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
  }

看看那里发生了什么?我们只是习惯于f2“提升”f1(Int, Int) => Intto (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(这里是A,BCare all Intfor f1)并返回另一个类型的函数(Option[A], Option[B]) => Option[C]。现在,我们不必再使用那些尴尬的嵌套maps 和flatMaps 了。你可以这样做:

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

当然,您也可以定义lift3lift4等,但只定义一个lift1函数并使用柯里化来完成其余部分可能更容易。

当然,lift如果您知道如何拆开和组装您要提升到的类型,您只能使用一个函数。例如,如果Some是一个具有私有unapply方法的对象,并且不可能对其进行模式匹配,那么您将无法提升f1. 如果 for 的构造函数Some是私有的并且你不可能创建 new 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
}
于 2020-06-24T19:38:04.130 回答