Scala Basics Continued for Python Developers
Classes
Inheritance
In Scala, inheritance works in the same way as Java. You could extend the parent class either overriding the methods or building on top of parent class.
class Animal(name: String, age: Int, family: String)
class Mammalian(name: String, age: Int) extends Animal(name, age, "Mammalian") {}
class Insect(name: String, age: Int) extends Animal(name, age, "Insect"){}
class Bird(name: String, age: Int) extends Animal(namge, age, "Bird"){}
// Both blueWhale1 and blueWhale2 have the same attributes
val blueWhale1 = new Animal("Churchill", 3, "Mammalian")
val blueWhale2 = new Mammalian("Churchill", 3)
Traits
If you want to use functionality of other class and do not have direct inheritance between two classes, trait
s are very useful for mixin for those applications.
trait Sound {
val volume: Int
}
trait Move {
val speed: Int
}
class Snake extends Animal("Black Mamba", 4, "Insect") with Sound with Move {
val volume = 5
val speed = 20
}
// snake is an animal with sound and speed
snake.volume // prints 5
snake.speed // prints 20
Partial Function Call
In the previous post, if I want to use closure-like function factories, I showed how one may could apply currying to leave one or more parameters in the function call. Instead of curring, one could leave out one of the parameters in the function call and still be able to get a function. So, let’s assume I have the following adderFactory
:
def adderFactory(x: Int, y: Int) : Int = {
x + y
}
val adder10 = adderFactory(10, _: Int)
adder10(5) // prints 15
Note that, this version is more flexible in terms of which variables are not used. If you know which variables are first used, and which would be used in the returned function, then you could use directly currying. If not, this approach would still works as in the following:
def mulSquareFactory(x: Int, y: Int, z: Int) : Int = {
x * y * y * z
}
val power15 = mulSquareFactory(3, _: Int, 5)
power15(4) // prints 240 => 3 * 4 * 4 * 5
Also, I could curry the multiple argument functions however I like.
val curriedMulSquareFactory = (mulSquareFactory _).curried
curriedMulSquareFactory(3)(4)(5) // prints 240
val power15 = curriedMulSquareFactory(3)(_: Int)(5)
In Python, we could use decorators to to be able to use somewhat similar paradigm, but currying is more powerful as you could leave out parameters and then could pass in later steps.
Variable Argument Function Call
In Python, I could capture the variable arguments by position using *
, and variable arguments by keywords **
.
def to_title(*args):
return [ii.lower().title() for ii in args]
titled_names = to_title('elephant', 'bee', 'horse')
print(titled_names) // ['Elephant', 'Bee', 'Horse']
Similarly, (even systax resembles to Python), one can do the same thing in Scala as well using *
:
def toTitle(args: String*) = {
args.map { arg =>
arg.toLowerCase.capitalize
}
}
val titledNames = toTitle("elePhant", "bEe", "hoRSe")
ArrayBuffer(Elephant, Bee, Horse)
Functional Programming
Scala supports functional programming quite heavily in both programming language and other constructs that it uses. map
, reduce
, fold
and many more constructs are defined on powerful data structures.
Using anonymous functions, (since functions are first-class citizens), I could pass the function to other higher order functions.
We could also define our function and pass that function into map, and even better we could use combinators to be more expressive:
def area(r: Int): Double = {
math.Pi * r * r
}
val areas = radiusses.map(area)
// Using combinator, map function call become more expressive
val areas = radiusses map area
filter
is also similar except it accepts a predicate to be able to filter the list. Function combinators could be still used as in the case of map
.
def isEven(x: Int): Boolean = {
x % 2 == 0
}
// I could pass partially applied function as well
val evenRadiusses = radiusses.filter(isEven _)
// Function combinators work, too
val evenRadiusses = radiusses filter isEven
println(evenRadiusses) // prints Vector(2, 4, 6, 8, 10)
Function Composition
We could have function compoosition as well, let’s assume we have the following two functions:
// f(x) => x^2 (symbolic expression)
def f(x: String): String = {
return "(" + x + ")^2"
}
// g(x) => (x + 1) (symbolic expression)
def g(x: String): String = {
return "(" + x + "+1)"
}
I could compose this functions using partially applied functions with compose
as in the following:
// first calls g and then f (similar to mathematical definition)
val fComposesG = f _ compose g _
fComposesG("3") // prints ((3+1))^2
If we want to reverse the order in the function call, we could use andThen
(or simply reverse the order using compose
):
val gComposesF = g _ compose f _
val fAndThenG = f _ andThen g _
fAndThenG("3") // ((3)^2+1)
gComposesF("3") // ((3)^2+1)
Pattern Matching
One of the mostly praised aspect of the language. Quite comprehensive and even sometimes replaces conditional statements in order to control the flow of the program.
val name = "Bugra"
name match {
case "Bugra" => "It is my name!"
case "John" => "It is John's name"
case _ => "We do not know who has this name" // It will match everything
}
Pattern matching could be also used for matching different types as well. But most importantly, if you want to match objects, you could use case class
es in order to match based on attributes of an object which is very useful as it completely gets rid of writing custom functions.
Case Classes
case class Animal(name: String, age: Int, family: String)
// Note that I am not using new keyword for case classes
val blueWhale = Animal("Mobby Dick", 4, "Mammalian")
val bluishWhale = Animal("Mobby Dick", 4, "Mammalian")
blueWhale == bluishWhale // prints True
Case classes are great for pattern matching as well: