The Concept of Lifting in Scala
- Transform Methods to Functions
- Transform Pure Functions to Functors in Scala
- Transform Partial Functions to Functions in Scala
- Monad Transformers in Scala
- Conclusion
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.