The Concept of Lifting in Scala

Suraj P Feb 02, 2024
  1. Transform Methods to Functions
  2. Transform Pure Functions to Functors in Scala
  3. Transform Partial Functions to Functions in Scala
  4. Monad Transformers in Scala
  5. Conclusion
The Concept of Lifting in Scala

This article will talk about lifting in the Scala programming language. Lifting has different meanings; it depends on the context we use it.

Let’s look at them one by one.

Transform Methods to Functions

We sometimes encounter situations where we want to transform methods into functions. This is one example of lifting.

For example, let’s say we have the following methods:

def mul(x: Int) = x*5
def isEven(x: Int) = x%2==0

Now, we can compose these methods as below:

isEven(mul(4))

But what if we want this composition to happen in a functional way? Then we have to transform these above methods into functions.

That can be done by lifting them using the _ symbol in Scala.

val mulAns = mul _
val isEvenAns = isEven _

Now, we can functionally compose them as below:

( mulAns andThen isEvenAns)(5)

Full working code:

object MyClass {
    def mul(x: Int) = x*5
    def isEven(x: Int) = x%2==0

    def main(args: Array[String])
    {
        println(isEven(mul(4)));

        val mulAns = mul _
        val isEvenAns = isEven _

        println(( mulAns andThen isEvenAns)(5))
    }
}

Output:

true
false

Explanation:

The first output is true as 4 is multiplied by 5, which gives 20. This 20 is passed as a parameter to the isEven function.

The second output is false as 5 is multiplied by 5 first, and then its result is checked in the isEven function.

Transform Pure Functions to Functors in Scala

Functor refers to the mapping between categories, somewhat like a function of entities or objects. For instance, we have categories A and B and a functor F that maps A's objects to B's objects.

trait Functor[F[_]] {
    def mapping[A, B](X: F[A])(f: A => B): F[B]

    def lifting[A, B](f: A => B): F[A] => F[B] =
        X => map(X)(f)
}

In this context, lifting refers to taking a function from A=>B and transforming it into a functor by making it a function of the form F[A] => F[B]. When working with nested data types, this can be very useful.

Transform Partial Functions to Functions in Scala

In this context, lifting refers to the extension of the domain. Partial functions are kinds of functions that can be applied to the subdomains of values.

But at times, we might want to extend their domain, and lifting helps us achieve that.

Let’s suppose we have a partial function that gives the square root of a positive number.

val sqrt: PartialFunction[Double, Double] =
{
    case temp if temp >= 0 => Math.sqrt(temp)
}

Now, we can extend the domain of our partial function using the lift method to make it look more elegant:

def getSqrtRootPartialFunction(t: Double) =
{
    sqrt.lift(t).map(ans => s"Square root of ${t} is ${ans}")
        .getOrElse(s"Cannot calculate square root for t")
}

Full working code:

object MyClass {
     val sqrt: PartialFunction[Double, Double] = {
        case temp if temp >= 0 => Math.sqrt(temp)
    }

    def getSqrtRootPartialFunction(t: Double) = {
        sqrt.lift(t).map(ans => println(s"Square root of ${t} is ${ans}"))
           .getOrElse(println(s"Cannot calculate square root for t"))
    }

    def main(args: Array[String])
    {
        getSqrtRootPartialFunction(35)
    }
}

Output:

Square root of 35.0 is 5.916079783099616

Explanation:

So, what lifting of the sqrt function has done here is that it extended the domain to whole double from PartialFunction[Double, Double] to a Function[Double, Option[Double]].

Sometimes, lifting partial functions is used to avoid the index out of bound exception.

Seq("1", "2", "3").lift(1) // Some("2")

Seq("1", "2", "3").lift(7) // none returned

The domain has been extended from String to Option[String]. So, when we try to access an out of bound value, the lifted Seq returns None instead of throwing an out of bound exception.

Monad Transformers in Scala

Combining monads together is done using monad transformers. Let’s say we have two Futures:

val helloWorld: Future[Option[String]] = Future.successful(Some("hello world"))
val language: Future[String] = Future.successful("this is Scala language")

Now, as both the Futures have different domains, we can use monad transformers by lifting language to OptionT. By this, we can handle the result in a more consistent way:

def MonadTransformer() = {
    val message: OptionT[Future, String] = for {
        hello <- OptionT(helloWorld)
        name  <- OptionT.liftF(language)
    } yield println(s"$hello $name")

    val result: Future[Option[String]] = message.value

    Await.result(result, 1 second)
}

Conclusion

In this article, we have learned the concept of lifting and what it means in different contexts. It is very useful to write our code in a more composable and idiomatic manner, allowing us to focus more on the business logic of the code rather than wasting time just building different parts of it.

Author: Suraj P
Suraj P avatar Suraj P avatar

A technophile and a Big Data developer by passion. Loves developing advance C++ and Java applications in free time works as SME at Chegg where I help students with there doubts and assignments in the field of Computer Science.

LinkedIn GitHub

Related Article - Scala Function