此外,Lift框架也让问题更加混乱,因为它的名字中包含lifting,但它并不能回答这个问题。
在Scala中,“lifting”是什么?
这里有几个用途:
记住,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扩展(感谢@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
方法 - 这很简单。
例如,假设您有一个返回IO [Stream[A]]
的函数。这可以转换为单子变换器StreamT[IO, A]
。现在,您可能希望将某个其他值提升为IO[B]
,以便它也是一个StreamT
。你可以写成这样:
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]
请注意,任何扩展 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有关)遇到的 lifting 另一个用法是用一个参数为 f: A -> B
的函数重载一个参数为 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的逆过程。
如果将lifting定义为将部分函数PartialFunction[A, B]
转换为总函数A => Option[B]
,那么unlifting就是将总函数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))
MonadTrans
实例T
用于M
以及一个Monad
实例用于N
,那么可以使用T.liftM
将类型为N[A]
的值提升为类型为M[N, A]
的值。 - 846846846liftM
来表达,但是我没有弄明白如何正确地使用它。你们太棒了! - Dmitry Bespalovf
不应该是实例吗?而不是res0
吗? - srzhio