在Scala中什么是“提升”?


252

有时,当我在Scala生态系统中阅读文章时,会读到“提升” /“提升”一词。不幸的是,没有解释确切的含义。我做了一些研究,似乎提升与功能值或类似的东西有关,但是我找不到能以初学者友好的方式解释提升实际上是什么的文字。

通过Lift框架(它的名称具有提升功能)还存在其他混乱,但这无助于回答这个问题。

什么是Scala中的“提升”?

Answers:


290

有一些用法:

部分功能

请记住,a PartialFunction[A, B]是为域的某些子集定义的函数A(由isDefinedAt方法指定)。您可以将a“提升” PartialFunction[A, B]到中Function[A, Option[B]]。也就是说,在定义的功能A,但其值类型Option[B]

这是通过在上显式调用方法lift来完成的PartialFunction

scala> val pf: PartialFunction[Int, Boolean] = { case i if i > 0 => i % 2 == 0}
pf: PartialFunction[Int,Boolean] = <function1>

scala> pf.lift
res1: Int => Option[Boolean] = <function1>

scala> res1(-1)
res2: Option[Boolean] = None

scala> res1(1)
res3: Option[Boolean] = Some(false)

方法

您可以将方法调用“提升”到函数中。这称为eta扩展(为此感谢Ben James)。因此,例如:

scala> def times2(i: Int) = i * 2
times2: (i: Int)Int

我们通过使用下划线将方法提升为功能

scala> val f = times2 _
f: Int => Int = <function1>

scala> f(4)
res0: Int = 8

注意方法和功能之间的根本区别。res0是(函数)类型的实例(即,它是一个(Int => Int)

函子

一个仿函数(如定义scalaz)是一些“容器”(我用这个词非常松散),F这样,如果我们有一个F[A]和功能A => B,那么我们就可以拿到手的F[B](想想,例如,F = Listmap方法)

我们可以将该属性编码如下:

trait Functor[F[_]] { 
  def map[A, B](fa: F[A])(f: A => B): F[B]
}

这与能够将功能“提升” A => B到函子域是同构的。那是:

def lift[F[_]: Functor, A, B](f: A => B): F[A] => F[B]

也就是说,如果F是一个函子,并且我们有一个函数A => B,那么我们有一个函数F[A] => F[B]。您可能会尝试实现该lift方法-这很简单。

Monad变形金刚

就像hcoopz在下面说的(我刚刚意识到,这将使我不必编写大量不必要的代码),术语“ lift”在Monad Transformers中也具有含义。回想一下,单子变压器是将“单子”彼此堆叠(单子不组成)的一种方式。

例如,假设您有一个返回的函数IO[Stream[A]]。可以将其转换为monad变压器StreamT[IO, A]。现在,您可能希望“提升”其他一些价值,IO[B]也许这也是一个价值StreamT。您可以这样写:

StreamT.fromStream(iob map (b => Stream(b)))

或这个:

iob.liftM[StreamT]

这就引出了一个问题:为什么我要将IO[B]a 转换成a StreamT[IO, B]。答案将是“利用合成的可能性”。假设您有一个功能f: (A, B) => C

lazy val f: (A, B) => C = ???
val cs = 
  for {
    a <- as                //as is a StreamT[IO, A]
    b <- bs.liftM[StreamT] //bs was just an IO[B]
  }
  yield f(a, b)

cs.toStream //is a Stream[IO[C]], cs was a StreamT[IO, C]

12
值得一提的是,“将方法提升为功能”通常称为eta扩展
本·詹姆斯

7
深入研究scalaz,与monad变压器有关的提升也出现了。如果我有一个实例用于和例如,然后可以用来提升类型的值到类型的值。MonadTransTMMonadNT.liftMN[A]M[N, A]
846846846

谢谢本,hcoopz。我已修改答案
oxbow_lakes

完善!还有一个理由要说:Scala-最好的。可以将其提升给Martin Odersky&Co-最好的。我什至愿意使用liftM它,但没有设法理解如何正确地做到这一点。伙计们,你真摇滚!
德米特里·贝斯帕洛夫

3
在“ 方法”部分,... res0是(函数)类型(Int => Int)的实例(即,它是一个值)...f应该是实例,不是res0吗?
srzhio

21

我在论文中遇到的其他提升(不一定是与Scala相关的提升)的用法是重载f: A -> Bwith中的函数f: List[A] -> List[B](或集合,多集合等)。这通常用于简化形式化,因为然后f将其应用于单个元素还是应用于多个元素都没有关系。

这种超载通常是声明式的,例如,

f: List[A] -> List[B]
f(xs) = f(xs(1)), f(xs(2)), ..., f(xs(n))

要么

f: Set[A] -> Set[B]
f(xs) = \bigcup_{i = 1}^n f(xs(i))

或命令式地

f: List[A] -> List[B]
f(xs) = xs map f

5
这是oxbow_lakes描述的“提升为函子”。
本·詹姆斯

6
@BenJames确实是这样。为了我的辩护:当我开始写我的文章时,oxbow_lakes的答案还不存在。
Malte Schwerhoff

20

请注意,任何扩展的集合PartialFunction[Int, A](如oxbow_lakes所指出的)都可以被取消;因此例如

Seq(1,2,3).lift
Int => Option[Int] = <function1>

从而将部分函数转换为总函数,将集合中未定义的值映射到None

Seq(1,2,3).lift(2)
Option[Int] = Some(3)

Seq(1,2,3).lift(22)
Option[Int] = None

此外,

Seq(1,2,3).lift(2).getOrElse(-1)
Int = 3

Seq(1,2,3).lift(22).getOrElse(-1)
Int = -1

这显示了一种避免索引超出范围异常的巧妙方法。


6

还有提举,这是提举的逆过程。

如果提升定义为

把部分函数PartialFunction[A, B]变成全部函数A => Option[B]

那么解除

将全部功能A => Option[B]变成部分功能 PartialFunction[A, B]

Scala标准库定义Function.unlift

def unlift[T, R](f: (T)Option[R]): PartialFunction[T, R]

例如,play-json库提供了扩展以帮助构建JSON序列化程序

import play.api.libs.json._
import play.api.libs.functional.syntax._

case class Location(lat: Double, long: Double)

implicit val locationWrites: Writes[Location] = (
  (JsPath \ "lat").write[Double] and
  (JsPath \ "long").write[Double]
)(unlift(Location.unapply))
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.