0% found this document useful (0 votes)
2K views21 pages

Scala Constructs: Concepts of Functional Programming

This document provides an overview of key concepts in functional programming and Scala including pure functions, immutability, referential transparency, higher-order functions, and Scala collections. It discusses functional programming paradigms like avoiding side effects and mutable state. It also demonstrates Scala code for defining functions, declaring variables, and using collections like lists and maps.

Uploaded by

Rahul S.Kumar
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
2K views21 pages

Scala Constructs: Concepts of Functional Programming

This document provides an overview of key concepts in functional programming and Scala including pure functions, immutability, referential transparency, higher-order functions, and Scala collections. It discusses functional programming paradigms like avoiding side effects and mutable state. It also demonstrates Scala code for defining functions, declaring variables, and using collections like lists and maps.

Uploaded by

Rahul S.Kumar
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
You are on page 1/ 21

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)
    }
}

You might also like