287

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?

0

4 Answers 4

323

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] 
Sign up to request clarification or add additional context in comments.

3 Comments

Delving further into scalaz, lifting also comes up in relation to monad transformers. If I have a MonadTrans instance T for M and a Monad instance for N, then T.liftM can be used to lift a value of type N[A] to a value of type M[N, A].
Perfect! Just one more reason to say: Scala - the best. Which could be lifted to Martin Odersky & Co - the best. I even would to use liftM for that, but didn't manage understand how to do that properly. Guys, you're rock!
In Methods section ...res0 is an instance (i.e. it is a value) of the (function) type (Int => Int)... Shouldn't f be an instance, not res0?
27

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.

Comments

21

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 

2 Comments

This is the "lifting into a functor" which oxbow_lakes describes.
@BenJames True indeed. To my defence: oxbow_lakes' answer wasn't there yet when I started to write mine.
6

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 function A => Option[B]

then unlifting is

turning a total function A => Option[B] into a partial function PartialFunction[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)) 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.