有时当我阅读Scala生态系统的文章时,我读到术语“提升”/“被提升”。不幸的是,它没有解释这到底是什么意思。我做了一些研究,似乎举重与功能值或类似的东西有关,但我无法找到一篇文章,以初学者友好的方式解释什么是举重。
Lift框架在其名称中有提升功能,但它并不能帮助回答这个问题。
Scala中的“提升”是什么?
有时当我阅读Scala生态系统的文章时,我读到术语“提升”/“被提升”。不幸的是,它没有解释这到底是什么意思。我做了一些研究,似乎举重与功能值或类似的东西有关,但我无法找到一篇文章,以初学者友好的方式解释什么是举重。
Lift框架在其名称中有提升功能,但它并不能帮助回答这个问题。
Scala中的“提升”是什么?
当前回答
还有一种是不提举,这是与提举相反的过程。
如果吊装定义为
将偏函数PartialFunction[a, B]转换为总数 选项[B]
那么解除是
将total函数a => Option[B]变成partial函数 PartialFunction [A, B]
Scala标准库定义了Function。unlift作为
[T, R](f: (T)⇒选项[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))
其他回答
还有一种是不提举,这是与提举相反的过程。
如果吊装定义为
将偏函数PartialFunction[a, B]转换为总数 选项[B]
那么解除是
将total函数a => Option[B]变成partial函数 PartialFunction [A, B]
Scala标准库定义了Function。unlift作为
[T, R](f: (T)⇒选项[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))
注意,任何扩展了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
这展示了一种避免索引越界异常的简洁方法。
我在论文(不一定是与scala相关的论文)中遇到的另一种提升用法是用f: List[a] -> List[B](或sets, multisets,…)重载f: a- > B中的函数。这通常用于简化形式化,因为f应用于单个元素还是多个元素并不重要。
这种重载通常以声明的方式完成,例如,
f: List[A] -> List[B]
f(xs) = f(xs(1)), f(xs(2)), ..., f(xs(n))
or
f: Set[A] -> Set[B]
f(xs) = \bigcup_{i = 1}^n f(xs(i))
或强制性地,例如:
f: List[A] -> List[B]
f(xs) = xs map f
有一些用法:
PartialFunction
记住,PartialFunction[a, B]是为域a的某个子集定义的函数(由isDefinedAt方法指定)。你可以将一个PartialFunction[a, B]“提升”为一个Function[a, Option[B]]。也就是说,定义在整个a上的函数,但其值类型为Option[B]
这是通过显式调用PartialFunction上的lift方法来完成的。
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[A]和一个函数A =>,那么我们可以得到一个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所说(我刚刚意识到这将使我免于编写大量不必要的代码),术语“升降机”在Monad变形金刚中也有意义。回想一下,单子变形金刚是一种将单子叠在一起的方式(单子不构成)。
例如,假设你有一个返回IO[Stream[a]]的函数。这可以转换为单变量转换器StreamT[IO, A]。现在你可能希望“提升”IO[B]的一些其他值,也许它也是一个流。你可以这样写:
StreamT.fromStream(iob map (b => Stream(b)))
或:
iob.liftM[StreamT]
这就引出了一个问题:为什么我想要将IO[B]转换为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]