Transition from java OOPs to scala FP-3
Folks , I hope you had fun time contending functions as pure or impure ones in context to my previous blog . If not , i will get into its depth in a while and give you enough reasons why you should adhere to pure functions as far as possible .
If you haven't browsed yet ,i would strongly recommend you to peek through the contents shared in my previous two blogs , links below :
Following are some of the arguments , why i would prefer pure functions over impure ones :
- Function signature says everything about the function i.e. they are easier to reason about .
- They are easy to compose .
val composedFunction= function1(a).function2(b).function3(c)
3. They are easier to test and debug .
4. They can easily be used in parallel programming .
There are plenteous number of positive arguments about why one should go for pure functional programming paradigm avoiding any side-effects . But , it is not always feasible to write and compose pure functions and avoid side-effects. You will have to deal with side-effects at some place in your code . To reduce side-effects(mostly IOs) and make programs more functional , we would be using some of the category theory constructs known as Functors , Monads , State Monads , IO Monads and others .You don’t need to worry about them as of now , as i will cover all of them in great depth in my series.
In my previous blog , i shared a list of topics and grouped them in a sequence i would be covering them . Let us continue from there . We already covered basics of functions and their representations , now let us peek into implicits .
Scala Implicits
When we enter into the world of Scala as a beginner, we experience tough time understanding and using implicits . Let me demystify them quickly for you. Some people talk of them in terms of dependency injection , but they are just context values , you would always need to do your computing . e.g.
def demoImplicits(param: String = "Hello")(implicit name: String): String =
{
s"${param} your implicit parameter is ${name}"
}
implicit val str="implicit value"
demoImplicits()O/P in Scala REPL :demoImplicits: (param: String)(implicit name: String)Stringstr: String = implicit valueres8: String = Hello your implicit parameter is implicit value
You might have guessed what is happening in the code above .We have a function demoImplicits that takes two parameters one is implicit and one is normal . You will have to present the implicit parameter in the context of execution to run the program successfully . So, if the scala compiler is not able to find one , you will get an error .Also , if you have more than one implicit defined in the context then you will get ambiguous implicit error .
I will list down some important points in respect to scala implicits :
- Implicits can be in many forms : parameters as shown above , implicit classes (providing helper methods for types ) and type classes .Examples below :
Implicit class :
object StringHelper {
implicit class StringOps(str: String) {
def toUC = str.toUpperCase() + "!"
def isQn = str.endsWith("?")
}
}
import StringHelper._
"Hello world".toUC
"How are you?".isQnO/P in Scala REPL :defined object Helpersimport Helpers._
res0: String = HELLO WORLD!
res1: Boolean = true
you can observe how toUC and isQn are acting as methods on string instances as they were methods inside String API but they aren’t !
Type class implicits :
def sum[A: Monoid](values: Seq[A]): A = {
val ev = implicitly[Monoid[A]]
values.foldLeft(ev.zero)(ev.plus)
}
P.S. implicitly
is just a regular function in Predef.scala that basically takes a single implicit parameter, gives it a name and returns it. Looks like this:
def implicitly[T](implicit e: T) = e
Implicits in scala are powerful tools of the language which can be used in different context to achieve different goals. Comes without saying that because of it’s non explicit nature its easy to get things wrong so use it carefully .
2. Implicits are searched by scala compiler ! The corrected implicit parameter precedence to remember are :
- implicits visible to current invocation scope via local declaration, imports, outer scope, inheritance, package object that are accessible without prefix.
- implicit scope, which contains all sort of companion objects and package object that bear some relation to the implicit’s type which we search for (i.e. package object of the type, companion object of the type itself, of its type constructor if any, of its parameters if any, and also of its supertype and supertraits).
If at either stage we find more than one implicit, static overloading rule is used to resolve it.
Scala Building Blocks
I want to give refresher highlights for the basic building blocks in scala to enlighten your memory again ! . Following are the built in types in scala with inheritance diagram :
So , Any is the super class of everything just like object class in java and rest all is obvious from the above visual . Next are Classes , Case Classes , Traits and Objects . Lets look into them one by one quickly .
- Scala Classes :
Scala class is a user-defined blueprint or prototype from which objects are created. Or in other words, a class combines the fields and methods(member function which defines actions) into a single unit. Basically, in a class constructor is used for initializing new objects, fields are variables that provide the state of the class and its objects, and methods are used to implement the behavior of the class and its objects. Syntax to declare a class in scala is as follows :
class Greetor extends Actor{
def receive={
case WhoToGreet(who) => println(s"hello $who")
}
}
2 . Scala Objects
Scala is more object-oriented than Java because in Scala, we cannot have static members. Instead, Scala has singleton objects. A singleton is a class that can have only one instance, i.e., Object. You create singleton using the keyword object instead of class keyword. Since you can’t instantiate a singleton object, you can’t pass parameters to the primary constructor. You already have seen all the examples using singleton objects where you called Scala’s main method. Syntax of Object in scala is as follows :
object HelloWorld extends App {
def print: String = "Hello Functional Programming ..."
print
}
3. Case Classes and Case Objects
Scala case classes are just regular classes which are immutable by default and support extraction through pattern matching.It uses equals method to compare instance . It does not use new keyword to instantiate object.All the parameters listed in the case class are public and immutable by default.A case class generates an object with apply method as default . This apply gets called on instance creation which acts as syntactic sugar .
sealed trait Json // Traits explained below
final case class JsString(value: String) extends Json
final case class JsInteger(value: Int) extends Json
final case class JsDouble(value: Double) extends Json
final case class JsObject(get: Map[String, Json]) extends Json
case object JsNull extends Json
4. Traits
Traits are used to share interfaces and fields between classes. They are similar to Java 8’s interfaces. Classes and objects can extend traits, but traits cannot be instantiated and therefore have no parameters.Traits become especially useful as generic types and with abstract methods. Following is a simple example to demonstrate traits in practice :
trait Iterator[A] {
def hasNext: Boolean
def next(): A
}class IntIterator(to: Int) extends Iterator[Int] {
private var current = 0
override def hasNext: Boolean = current < to
override def next(): Int = {
if (hasNext) {
val t = current
current += 1
t
} else 0
}
}
val iterator = new IntIterator(10)
iterator.next() // output is 0
iterator.next() // output is 1
That ends the scala building blocks ! Would be sharing further notes in the next part of the series . Till then happy coding :)