问题
Sometimes when I read articles in the Scala ecosystem I read the term "lifting" / "lifted". Unfortunately, it is not explained what that exactly means. I did some research, and it seems that lifting has something to do with functional values or something like that, but I was not able to find a text that explains what lifting actually is about in a beginner friendly way.
There is additional confusion through the Lift framework which has lifting in its name, but it doesn't help answer the question.
What is "lifting" in Scala?
回答1:
There are a few usages:
PartialFunction
Remember a PartialFunction[A, B]
is a function defined for some subset of the domain A
(as specified by the isDefinedAt
method). You can "lift" a PartialFunction[A, B]
into a Function[A, Option[B]]
. That is, a function defined over the whole of A
but whose values are of type Option[B]
This is done by the explicit invocation of the method lift
on 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)
Methods
You can "lift" a method invocation into a function. This is called eta-expansion (thanks to Ben James for this). So for example:
scala> def times2(i: Int) = i * 2
times2: (i: Int)Int
We lift a method into a function by applying the underscore
scala> val f = times2 _
f: Int => Int = <function1>
scala> f(4)
res0: Int = 8
Note the fundamental difference between methods and functions. res0
is an instance (i.e. it is a value) of the (function) type (Int => Int)
Functors
A functor (as defined by scalaz) is some "container" (I use the term extremely loosely), F
such that, if we have an F[A]
and a function A => B
, then we can get our hands on an F[B]
(think, for example, F = List
and the map
method)
We can encode this property as follows:
trait Functor[F[_]] {
def map[A, B](fa: F[A])(f: A => B): F[B]
}
This is isomorphic to being able to "lift" the function A => B
into the domain of the functor. That is:
def lift[F[_]: Functor, A, B](f: A => B): F[A] => F[B]
That is, if F
is a functor, and we have a function A => B
, we have a function F[A] => F[B]
. You might try and implement the lift
method - it's pretty trivial.
Monad Transformers
As hcoopz says below (and I've just realized that this would have saved me from writing a ton of unnecessary code), the term "lift" also has a meaning within Monad Transformers. Recall that a monad transformers are a way of "stacking" monads on top of each other (monads do not compose).
So for example, suppose you have a function which returns an IO[Stream[A]]
. This can be converted to the monad transformer StreamT[IO, A]
. Now you may wish to "lift" some other value an IO[B]
perhaps to that it is also a StreamT
. You could either write this:
StreamT.fromStream(iob map (b => Stream(b)))
Or this:
iob.liftM[StreamT]
this begs the question: why do I want to convert an IO[B]
into a StreamT[IO, B]
?. The answer would be "to take advantage of composition possibilities". Let's say you have a function 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]
回答2:
Another usage of lifting that I've come across in papers (not necessarily Scala-related ones) is overloading a function from f: A -> B
with f: List[A] -> List[B]
(or sets, multisets, ...). This is often used to simplify formalisations because it then doesn't matter whether f
is applied to an individual element or to multiple elements.
This kind of overloading is often done declaratively, e.g.,
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))
or imperatively, e.g.,
f: List[A] -> List[B]
f(xs) = xs map f
回答3:
Note any collection that extends PartialFunction[Int, A]
(as pointed out by oxbow_lakes) may be lifted; thus for instance
Seq(1,2,3).lift
Int => Option[Int] = <function1>
which turns a partial function into a total function where values not defined in the collection are mapped onto None
,
Seq(1,2,3).lift(2)
Option[Int] = Some(3)
Seq(1,2,3).lift(22)
Option[Int] = None
Moreover,
Seq(1,2,3).lift(2).getOrElse(-1)
Int = 3
Seq(1,2,3).lift(22).getOrElse(-1)
Int = -1
This shows a neat approach to avoid index out of bounds exceptions.
回答4:
There is also unlifting, which is the inverse process to lifting.
If lifting is defined as
turning a partial function
PartialFunction[A, B]
into a total functionA => Option[B]
then unlifting is
turning a total function
A => Option[B]
into a partial functionPartialFunction[A, B]
Scala standard library defines Function.unlift as
def unlift[T, R](f: (T) ⇒ Option[R]): PartialFunction[T, R]
For example, play-json library provides unlift to help with construction of JSON serialisers:
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))
来源:https://stackoverflow.com/questions/17965059/what-is-lifting-in-scala