有时当我阅读 Scala 生态系统中的文章时,我会阅读术语“提升”/“提升”。不幸的是,没有解释这究竟意味着什么。我做了一些研究,似乎提升与功能价值或类似的东西有关,但我无法找到以初学者友好的方式解释提升实际上是什么的文本。
名称中带有提升功能的Lift框架存在额外的混淆,但它无助于回答问题。
Scala 中的“提升”是什么?
有时当我阅读 Scala 生态系统中的文章时,我会阅读术语“提升”/“提升”。不幸的是,没有解释这究竟意味着什么。我做了一些研究,似乎提升与功能价值或类似的东西有关,但我无法找到以初学者友好的方式解释提升实际上是什么的文本。
名称中带有提升功能的Lift框架存在额外的混淆,但它无助于回答问题。
Scala 中的“提升”是什么?
有几种用法:
请记住,aPartialFunction[A, B]
是为域的某个子集定义的函数A
(由isDefinedAt
方法指定)。您可以“提升” aPartialFunction[A, B]
为Function[A, Option[B]]
. 也就是说,在整个上定义的函数,A
但其值是类型Option[B]
这是通过显式调用 on 上的方法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-expansion(感谢 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 = List
想想map
方法)
我们可以对这个属性进行如下编码:
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
方法 - 这非常简单。
正如hcoopz在下面所说的(我刚刚意识到这可以让我免于编写大量不必要的代码),术语“lift”在Monad Transformers中也有含义。回想一下,单子转换器是一种将单子“堆叠”在彼此之上的方式(单子不组合)。
例如,假设您有一个返回IO[Stream[A]]
. 这可以转换为 monad 转换器StreamT[IO, A]
。现在您可能希望“提升”一些其他值 anIO[B]
也许它也是一个StreamT
. 你可以这样写:
StreamT.fromStream(iob map (b => Stream(b)))
或这个:
iob.liftM[StreamT]
这引出了一个问题:我为什么要将 an 转换IO[B]
为 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]
请注意,任何扩展的集合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
这显示了一种避免索引越界异常的巧妙方法。
我在论文中遇到的另一种提升f: A -> B
用法(不一定是与 Scala 相关的)是从with 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
还有unlifting,这是 Lifting 的逆过程。
如果起重定义为
将部分函数
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 库提供了unlift来帮助构建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))