Scala Constructs
Concepts of Functional Programming
Functional Programming (FP) is a programming paradigm.
It is about the style of how you develop the blocks of your program. It focuses not on
how to compute, but what to compute. Functions are first class citizens in an FP.
Let us learn a few key concepts that are commonly adopted in any functional
programming language viz.,
Pure functions
No side effects
Immutability
Referential transparency
Higher-order functions
Side Effects
You must have written or will be writing lot of functions in your code. Have you
realized doing any of the following actions in a function will result in side effects?
Changing the value of a variable
Updating the data structure already in place
Setting a field in an object
Throwing exception
Waiting due to an error
Printing output/content to console
Dealing with files (read/write) and so on.
Functions that are written without any side effects are called Pure Functions.
Immutability
If you cannot change something, that is immutable.
An immutable object (unchangeable object) is an object whose value or state cannot
be modified once created.
Immutability is not about restricting you to do something. It is more about dealing it
differently.
Let us see a quick example.
val number: Int = 20
number = 30
number = 30 gives a compilation error in Scala (val is the keyword in Scala to define
an immutable object)
A function is supposed to be referentially transparent, if for the same input it
always produces the same output.
Can you think of an example which is against this? Let us look at the following pseudo
code (too early to write Scala code!)
function getTimeAdded(input: Long) = return (System.currentTime + input)
The output of the above function will not be same even if you call with the same
argument, "at different times." So the above function is not referentially transparent.
Why Scala
Scala is
both functional and object-oriented.
a modern multi-paradigm programming language.
concise, elegant, and type-safe.
Popular Functional Programming languages
There are pure and impure functional languages. The list of impure languages is huge.
They are impure because they allow a different style of writing.
For example, Scala is object-oriented, not just functional.
Hello World Program
object HelloWorld{
def main()
{
println("Hello World")
}
}
Declaring and Using Variables
Let us go straight to a code snippet to understand some basic elements of the
language.
Read the comments carefully in the following code. Please note:
// is to comment a line of code and /* */ can be used to comment a block.
object Main extends App {
println("Hello Scala")
// initializing an immutable object/variable using val
val a: Int = 10
println(a)
// Re assignment to a val?? uncomment and see the compilation error.
// a = 20
// initializing a mutable variable using var
// a legal expression even though we haven't explicitly stated the data type of
`b`.
var b = 20
// Scala compiler can infer the data type as Int.
b = 30 // we can mutate the value of `b` since its declared as `var`
}
Here object Main inherits the main method of App. App is called as trait, which is
equivalent to interface in Java.
Declaring and Using Variables Contd...
To summarize,
Declaring a variable with var, makes it mutable
Declaring a variable with val makes it immutable
Reassignment of an immutable variable is not possible
Data type declaration happens after the variable name and a colon :
Data type declaration is not mandatory
In the absence of explicit data type declaration, Scala can infer the variable
type based on the value assigned
line of code or block of code can be commented using // or /* */
Higher Order Functions
Just like we pass variables or objects as arguments to functions, we can pass
around function itself as an argument to another function in Scala.
Even the return type of a function can be another function! These type of functions
are called as Higher Order Functions.
Function Definition Explained
Scala syntax is flexible, and there are different ways you can write a function from a
syntax point of view.
Let us look at a simple function definition first.
def addOne(arg: Int): Int = { arg + 1 }
def is the keyword to define a method or function.
addOne is a function that takes an integer as argument and returns another
integer as result.
Scala notation to express it: Int => Int (You can say Int gives Int !)
object Result{
def add(op1: Int, op2:Int): Int = { op1+op2 }
}
Create a Higher Order function
Let us consider the following code snippet, myHigherOrderFunction function that
takes two parameters.
First parameter or argument is a function (i.e. a function itself as an argument).
The function which is passed, when called, should be of type Int => Int. You
could relate this to a function definition in the previous section.
Second parameter is a value, a regular kind of parameter that we see.
def myHigherOrderFunction(argFn: Int => Int, argVal: Int): Int = {
println("Inside myHigherOrderFunction ")
println(s"\n Applying the arg function to argVal = $argVal")
argFn(argVal) // compute the argFn and return the result
}
Another takeaway: Look at the second print statement in the snippet and understand
how to substitute a variable value in a string.
Calling a Higher Order Function
val myNumber = 10
val result = myHigherOrderFunction(addOne, myNumber)
println(result)
The output of the above code would be 11. Do you know how that is done?
object Result{
var multiply = (a:Int,b:Int)=>a*b// Define your function literal he
re
}
Data Types in Scala
Function Literals
In Scala, functions can be expressed in function literal
For example: (x: Int) => x + 1 is a function literal
Those functions can be represented by objects, which are called function
values.
The above function can be read as, a function which takes an integer argument and
returns an integer result.
You can assign it to an object also as val f = (x: Int) => x + 1
Using Function Literal
Here is an example snippet to show to use function literals or function values:
def myHigherOrderFunction(argFn: Int => Int, argVal: Int): Int = {
argFn(argVal) // compute the argFn and return the result
}
If the literal is not assigned to an object, it can be used as
val result = myHigherOrderFunction((a: Int) => { a + 1 }, 12) .
If the same is assigned to an object f, it can be used as
val result = myHigherOrderFunction(f, 12) .
Both will give the same result.
Function literals are also known as anonymous functions.
Scala Collections
Collections let you organize large numbers of objects.
Scala has a rich collection library
In simple cases, you can throw a few objects into a set or a list and not think
much about it.
For trickier cases, Scala provides a general library with several collection types,
such as sequences, sets and maps.
Each collection type comes in two variants—mutable and immutable.
Most kinds of collections have several different implementations that have different
tradeoffs of speed, space, and the requirements on their input data.
Lists
Let us get started with List, one of the most used collections in Scala.
List, as the name goes, is similar to arrays. It is a collection of elements of the same
type.
Here is an example to create an integer list:
val intList: List[Int] = List(1,2,3).
As Scala is a language with lot of syntactic sugar and built-in type inference features,
programmers can write the above expression as
val intList = List(1,2,3) // this is valid too.
Map
Collections in Scala has a lot of built-in functions, and many of them are of
type higher order functions. Let us see an example.
// function to convert an integer to a string
def covertToString(arg: Int): String = arg.toString
Now, the following snippet would convert a list Integer values to a list of String,
using map function.
val newList: List[String] = intList.map(covertToString)
// convertToString is a function which is passed onto map function in list.
The function map in List type is a higher order function. It accepts a function that
operates on one element of a List at a time. The argument function maps one element
of list to another type say, U. i.e once you apply the map function on a list of
type List[T], the result you get back is a List[U].
Note: U can be same as T as well
Collection Hierarchy
Scala collections orderly differentiate immutable and mutable collections.
A mutable collection can be extended or updated in place. i.e. user can
include, change or exclude elements of a collection as a side effect.
By contrast, immutable collections, never change. Still, a user has operations
that simulate updates, removals, or additions. However, these operations will
return a new collection in each case and allow the old collection unchanged, as
well.
Collection Classes
The entire lot of collection classes are available in
the scala.collection package or one of the sub-packages
of scala.collection - generic, immutable, and mutable.
Most collection classes needed by client code exist in three variants, which
are located in packages scala.collection, scala.collection.immutable,
and scala.collection.mutable, respectively.
Each variant features distinct characteristics on mutability.
Note: By default, Scala always takes collection from the immutable
hierarchy.
For instance,
If you just write Set without any prefix, or without having imported Set from
somewhere, you will get an immutable set.
If you write Iterable, you will get an immutable iterable collection, because
these are the default bindings imported from the Scala package.
To have the mutable default versions, you have to write
explicitly collection.mutable.Set, or collection.mutable.Iterable
Filtering
There are plenty of built-in functions provided in Scala for all collections. We will look
at a few of the commonly used ones.
Here is an example of filter and _some more syntactic sugars in Scala. It is fun. Read
the commented part carefully.
object Main extends App {
val lst = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
// example for filtering all elements greater than 5
val g5 = lst.filter(i => i > 5)
println(g5)
}
The above statement also shows usage of an anonymous function. ie i => i >
5 is actually of type Int => Boolean. type of i is not explicitly given as Scala
understands that it is an Integer.
Writing it as i: Int => i > 5 is also correct.
Another simplification in terms of Syntactic sugar is to write the expression
as val g5 = lst.filter(_ > 5). _ (underscore) will be replaced by the
elements of the list by Scala compiler for you.
foreach
foreach is an example of a method that can have side effects. foreach does not
return anything. It can be used for writing the output to disk, database, printing, etc.
val intList = List(1,2,3,4,5)
intList.foreach(println) // foreach is of return type `Unit`
Above snippet will print the result to stdout.
Unit is the return type to mean there is nothing returned. Hence the methods of
return type Unit can have side effects, i.e., they are not pure functions.
Does it make sense with the functional programming concepts we learned?
ther Important Functions in Collections
Refer the scala library
Do some exercises on your own. We will cover few of the complex ones in the
upcoming courses.
def ListFilter(var1: Int) {
// Put your code here
val x = List.range(0,var1)
println(x)
val g = x.filter(_ % 2 ==0)
println(g)
}
What is flatMap?
We have already tried some examples with map. flatMap is very similar to map.
However, there are some differences concerning the functions that are passed onto
them. Both map and flatMap can be applied on many other data structures and where
they are supported.
Let us use List as an example here to understand about flatMap easily.
To make it clear,
map accepts a function that returns U for a given T. Here, T => U, hope you
remember the previous example about Int => Int. T and U are generic types
here.
flatMap accepts a function that returns a List[U] for every T. ie the function
passed onto it should be of type T => List[U]. The results are finally flattened
to create a single List[U].
It might be looking slightly complicated. Some examples would make it clear.
Example for flatMap
Following snippet gives an example for flatMap.
object Main extends App {
val intList = List(1,2,3,4,5)
def returnTwo(arg: Int): List[Int] = List(arg, arg)
val newList = intList.flatMap(returnTwo)
println(newList)
}
output of the above snippet would be List(1,1,2,2,3,3,4,4,5,5)
Hope that is clear now!
def flatMapUsage(var1: Int) {
// Put your code h
def listUntil(x: Int): List[Int] =
List.range(start = 1, end = (x + 1))
val s = listUntil(var1).flatMap(listUntil)
println(s)
println(s.length)
Objects
Scala is a pure object oriented language and it extends the use of classes, objects,
interfaces, etc. to programmers.
While working on building applications using Scala, you can decompose your program
into classes, objects, etc. to deal with complexities.
In other words, a class should be responsible for a reasonable amount of
functionality.
In the process of designing your classes, you can also design interfaces to those
classes that abstract away the details of their implementation.
Objects and Variables
When writing the code of a Scala program, you create and interact with objects. To
interact with a particular object, you can use a variable that refers to the object.
You can define a variable with either a val or var, and assign an object to it with =.
For example,
When you write val i = 1, you create an Int object with the value 1 and
assign it to a variable. In this case, a val named i.
Similarly, when you write var s = "Happy", you create a String object with the
value "Happy" and assign it to a variable, a var named as s.
Singleton Objects
One way in which Scala is more object-oriented than Java is that classes in Scala
cannot have static members.
Instead, Scala has singleton objects. A singleton object definition looks like a class
definition, except instead of the keyword class you use the keyword object. Here’s an
example:
Don't worry about the logic in the following snippet. However you may explore what is for
comprehension in Scala.
object simpleObjectExample {
val defaultValue = 20
def doSomething(arg: Int) = { arg * 2 * 3 }
}
You can use the functions or members as objectName.member.
for ex: simpleObjectExample.doSomething(100)
We have already used singleton objects in our previous sections, in various places. Do
you remember?
Classes
A class is considered as a blueprint for objects. Once you define a class, you can
create objects of it using the keyword new. For example, given the class definition:
class Home {
// class definition goes here
// define your members here (both variables and functions)
}
You can create Home objects (also known as instances of class Home) with:
new Home
A user can assign the object to a variable, for later reference and usage.
val h1 = new Home
you can construct many objects from one class:
val h2 = new Home
val h3 = new Home
You now have three Home object
Case Class
case classes are similar to classes. You need to a put a keyword case in front
of class.
Is that all ? No.
You can build objects of them without using new keyword.
How do I create a case class.
case class Fresco(specialisation: String, courseName: String)
Now creating an object of type Fresco is easy enough as
val obj = Fresco("modern data platform", "functional programming in
Scala")
There is no new keyword used, no setter methods used.
case classes can have methods just like normal classes
Pattern Matching
Pattern matching is very similar to switch case structures that you would have seen
other programming languages. However, Pattern Matching in Scala has lot more to
do!
Let us start with a simple example.
Matching on Values
val times = 1
times match {
case 1 => "one"
case 2 => "two"
// _ matches everything else
case _ => "some other number"
}
You can also use the concept called guards along with pattern matching. An example
is here.
// Note the if conditions given in case
times match {
case i if i == 1 => "one"
case i if i == 2 => "two"
case _ => "some other number"
}
Pattern Matching on types
We have seen one case of pattern matching, that was matching on values. Now let us
look at an example of matching on types, sounds interesting?
You can use a match to handle values of different types differently.
// Any is the super type of all built-in types in Scala.
In this example, argument o is matched against a type, whether it is Double or Int
before doing an operation.
def bigger(o: Any): Any = {
o match {
case i: Int if i < 0 => i - 1
case i: Int => i + 1
case d: Double if d < 0.0 => d - 0.1
case d: Double => d + 0.1
case text: String => text + "s"
}
}
Pattern matching on case classes
case classes are designed to be used with pattern matching.
abstract class Notification
case class Email(
sender: String,
title: String,
body: String) extends Notification
case class SMS(
caller: String,
message: String) extends Notification
case class VoiceRecording(
contactName: String,
link: String) extends Notification
Notification is an abstract super class which has three concrete Notification types
implemented with case classes Email, SMS, and VoiceRecording.
Pattern Matching on Case Classes Contd...
Now we can do pattern matching on these case classes:
def showNotification(notification: Notification): String = {
notification match {
case Email(email, title, _) =>
s"You got an email from $email with title: $title"
case SMS(number, message) =>
s"You got an SMS from $number! Message: $message"
case VoiceRecording(name, link) =>
s"you received a Voice Recording from $name! Click the link to hear it:
$link"
}
}
val someSms = SMS("12345", "Are you there?")
val someVoiceRecording = VoiceRecording("Tom", "voicerecording.org/id/123")
// prints You got an SMS from 12345! Message: Are you there?
println(showNotification(someSms))
// you received a Voice Recording from Tom! Click the link to hear it:
voicerecording.org/id/123
println(showNotification(someVoiceRecording))
def PatternMatching(var1: Int) {
var1 match {
case i: Int if i<=0 => println("Negative/Zero is input")
case i: Int if i%2==0 => println("Even number is given")
case i: Int if i%2!=0 => println("Odd number is given")
}
}
Traits
Traits are applied to share fields and interfaces between classes. These are same as
Java 8’s interfaces. Objects and classes can extend traits. However, traits cannot be
instantiated and hence have no parameters.
Defining a trait
A minimal trait is simply the keyword trait and an identifier:
ex: trait HairColor
extends is the keyword to inherit from a trait.
Traits Example
Let us look at an example
for trait.
trait BaseSoundPlayer {
def play
def close
def pause
def stop
def resume
}
If a class implements one trait it will use the extends keyword:
class Mp3SoundPlayer extends BaseSoundPlayer {
def play {}
def close {}
def pause {}
def stop {}
def resume {}
}
One trait can extend another trait:
If a class extends a trait but does not implement the methods defined in that trait, it must
be declared abstract:
Implementing Multiple Traits
If a class implements multiple traits, it will extends the first trait (or a class, or abstract
class), and then use with for other traits:
Here is an example
abstract class Animal {
def speak
}
trait WaggingTail {
def startTail
def stopTail
}
trait FourLeggedAnimal {
def walk
def run
}
class Dog extends Animal with WaggingTail with FourLeggedAnimal {
// implementation code here ...
}
class Rectangle(length : Int, breadth : Int)
{
def area()
{
println(length*breadth)
}
def perimeter()
{
val a = 2 * (length+breadth)
println(a)
}
}
object Result{
trait ArithmeticOperations {
val x :Int
val y: Int
def add: Int
def subtract: Int
def multiply: Int
def divide: Int
}
class Variables (xc: Int, yc: Int) extends ArithmeticOperations {
val x= xc
val y= yc
def add() = x + y
def subtract() = x - y
def multiply() = x * y
def divide() = x / y
}
}
Hands On -2
object Result {
/*
* Complete the 'mapHigherOrder' function below.
*
* The function accepts INTEGER_ARRAY args as parameter.
*/
def mapHigherOrder(args: Array[Int]) {
val intList :List[Int] = args.map(_.toInt).toList
// Put your code here
def isPerfectNumber(input: Int) :String = {
// Put your code here
if ( (for (x <- 2 to input/2 if input % x == 0) yield x).sum + 1 ==
input )
return "true"
else
return "false"
}
def myHigherOrderFunction(argFn: Int => String, argVal:List[Int]): List
[String] =
{ // Put your code here
return argVal.map(argFn)
}
println(myHigherOrderFunction(isPerfectNumber, intList))
}
}
HandsOn 1
object Result {
def ListHigherOrder(input: Int) {
// Put your code here
val intList = List.range(1,input+1)
def factorial(n: Int): Int = {
var f = 1
for(i <- 1 to n)
{
f = f * i;
}
return f
}
factorial(input)
def myHigherOrderFunction(fac: (Int) => Int, intList: List[Int]) =
intList.foreach((x: Int) => println(fac(x)))
myHigherOrderFunction(factorial, intList)
}
}