In our first chapter, we will learn the basic concepts of Functional Programing (FP), reactive programming, and the Scala language. These concepts are listed as follows:
Setting up a Scala development environment with Eclipse Scala IDE.
Basic constructs of the language like var, val, for, if, switch, and operator overload.
The difference between FP and object-oriented programming.
Principles of pure FP: immutability, no side effects, state discipline, composition, and higher order functions.
Concepts of FP such as lambda, recursion, for comprehensions, partial functions, Monads, currying, and functions.
Pattern Matcher, recursion, reflection, package objects, and concurrency.
Let's get going!
FP is not new at all. The very first implementation of FP is Lisp and is dated from the 1950s. Currently, we are living in a post-functional programming era, where we have the strong math principles and ideas from the 50s mixed with the most modern and beautiful piece of engineering, also know as the Java Virtual Machine (JVM). Scala is a post-functional programming language built on top of the JVM. Being on top of the JVM gives us a lot of benefits such as the following:
Scala is a post-functional programming language built on top of the JVM. Being on top of the JVM gives us a lot of benefits such as the following:
Reliability and performance: Java is used by 10 out of 10 top websites we have currently, like Netflix, Apple, Uber, Twitter, Yahoo, eBay, Yelp, LinkedIn, Google, Amazon, and many others. JVM is the best solution at scale and is battle-tested by these web-scale companies.
Native JVM eco-system: Full access to all of the Java ecosystem including frameworks, libraries, servers, and tools.
Operations leverage: Your operation team can run Scala in the same way they run Java.
Legacy code leverage: Scala allows you to easily integrate Scala code with Java code. This feature is great because it enables Java legacy system integration inside the box.
Java interoperability: A code written in Scala can be accessed in Java.
Scala was created in 2001 at EPFL by Martin Odersky. Scala is a strong static-typed language, and was inspired by another functional language called Haskell. Scala addresses several criticisms of the Java language, and delivers a better developer experience through less code and more concise programs, without losing performance.
Scala and Java share the same infrastructure as the JVM, but in terms of design, Scala is a different language in comparison with Java. Java is an imperative object-oriented language and Scala is a post-functional, multiparadigm programing language. FP works with different principles than object-oriented programing (OOP). OOP got very popular and well established in enterprise thanks to languages like Java, C#, Ruby, and Python. However, languages like Scala, Clojure, F#, and Swift are gaining a huge momentum, and FP has grown a lot in the last 10 years. Most of the new languages are pure functional, post-functional, or hybrid (like Java 8). In this book, you will see Scala code compared with Java code so you can see by yourself how Scala is way more compact, objective, and direct than Java and imperative OOP languages.
FP started at academia and spread to the world; FP is everywhere. Big Data and Stream processing solutions like Hadoop and Spark (built on top of Scala and Akka) are built on top of FP ideas and principles. FP spread to UI with RxJavaScript - you can even find FP in a database with Datomic (Clojure). Languages like Clojure and Scala made FP more practical and attractive to enterprise and professional developers. In this book, we will be exploring both principles and practical aspects of the Scala language.
FP is a way of thinking, a specific style of constructing and building programs. Having an FP language helps a lot in terms of syntax, but at the end of the day, it's all about ideas and developer mindset. FP favors disciplined state management and immutability in a declarative programming way rather than the imperative programming mostly used by OOP languages such as Java, Python, and Ruby.
FP has roots in math back to Lambda calculus - a formal system developed in the 1930s. Lambda calculus is a mathematical abstraction and not a programming language, but it is easy to see its concepts in programming languages nowadays.
Imperative programming uses statements to change the program state. In other words, this means you give commands to the program to perform actions. This way of thinking describes a sequence of steps on how the program needs to operate. What you need to keep in mind is the kind of style focus on how FP works in a different way, focusing on what the program should accomplish without telling the program how to do it. When you are coding in FP, you tend to use fewer variables, for loops, and IFS, and write more functions and make function composition.
The following are the CORE principles of FP:
Immutability
Disciplined state
Pure functions and no side effects/disciplined states
First class functions and high order functions
Type systems
Referential transparency
Let's understand these principles in detail.
The concept of immutability is the CORE of FP, and it means that once you assign a value to something, that value won't change. This is very important, because it eliminates side effects (anything outside of the local function scope), for instance, changing other variables outside the function. Immutability makes it easier to read code, because you know the function that you are using is a pure function. Since your function has a disciplined state and does not change other variables outside of the function, you don't need to look at the code outside the function definition. This sounds like you're not working with state at all, so how would it be possible to write professional applications this way? Well, you will change state but in a very disciplined way. You will create another instance or another pointer to that instance, but you won't change that variable's value. Having immutability is the key to having better, faster, and more correct programs, because you don't need to use locks and your code is parallel by nature.
Shared mutable state is evil, because it is much harder to scale and to run it concurrently. What is shared mutable state? A simple way to see it is as a global variable that all your functions have access to. Why is this bad? First of all, because it is hard to keep this state correct since there are many functions that have direct access to this state. Second, if you are performing refactoring, this kind of code is often the hardest to refactor as well. It's also hard to read this code. This is because you can never trust the local method, since your local method is just one part of the program. And with mutable state, you need to look up for all the functions that use that variable, in order to understand the logic. It's hard to debug for the very same reason. When you are coding with FP principles in mind, you avoid, as much as possible, having a shared mutable state. Of course you can have state, but you should keep it local, which means inside your function. This is the state discipline: you use state, but in a very disciplined way. This is simple, but it could be hard especially if you are a professional developer, because this aspect is now usual to see in enterprise languages such as Java, .NET, Ruby, and Python.
Pure functions are the ones with no side effects. Side effects are bad, because they are unpredictable and make your software hard to test. Let's say you have a method that receives no parameters and returns nothing--this is one of the worst things we could have, because how do you test it? How can you reuse this code? This is not what we call a pure function. What are the possible side effects? Database call, global variables, IO call, and so on. This makes sense, but you cannot have a program with just pure functions, because it won't be practical.
First-class means that the language treats functions as first-class citizens. In other words, it means having language support to pass functions as arguments to other functions and to return values as functions. First-class function also implies that the language allows you to store functions as variables or any other data structure.
Higher-order functions are related to First-class functions, but they are not the same thing. Higher-order functions often means language support for partial functional application and Currying. Higher-order functions are a mathematical concept where functions operate with other functions.
Partial functions are when you can fix a value (argument) to a particular function, which you may or may not change later on. This is great for function composition.
Currying is a technique to transform a function with multiple parameters in a sequence of functions with each function having a single argument. Scala language does not force currying, however, languages like ML and Haskell almost always use this kind of technique.
Type system is all about the compiler. The idea is simple: you create a type system, and by doing so, you leverage the compiler to avoid all kinds of mistakes and errors. This is because the compiler helps in making sure that you only have the right types as arguments, turn statements, function composition, and so on. The compiler will not allow you do make any basic mistakes. Scala and Haskell are examples of languages that are Strong-type. Meanwhile, Common Lisp, Scheme, and Clojure are dynamic languages that may accept wrong values during compilation time. One of the biggest benefits of the strong type system is that you have to write fewer tests, because the compiler will take care of several issues for you. For instance, if you have a function that receives a string, it could be dangerous, because you can pass pretty much anything in a string. However, if you have a function that receives a type called salesman, then you don't write a validation to check if it is a salesman. All this may sound silly, but in a real application, this saves lots of lines of code and makes you program better. Another great benefit of strong typing is that you have better documentation, as your code becomes your documentation, and it's way more clear what you can or can't do.
Referential transparency is a concept which works close with pure functions and immutability since your program has fewer assignment statements, and often when you have it, you tend to never change that value. This is great because you eliminate side effects with this technique. During program execution, any variable can be replaced since there are no side effects, and the program becomes referentially transparent. Scala language makes this concept very clear the moment you declare a variable.
Scala requires JVM to work, so we need get the JDK 8 before installing Scala. Go to the Oracle website, and download and install JDK 8 from http://www.oracle.com/technetwork/pt/java/javase/downloads/index.html.
Once you've downloaded Java, we need to add Java to the PATH
variable; otherwise, you can use the terminal. We do this as follows:
$ cd ~/ $ wget --no-cookies --no-check-certificate --header "Cookie: gpw_e24=http%3A%2F%2Fwww.oracle.com%2F; oraclelicense=accept-securebackup-cookie" " http://download.oracle.com/otn-pub/java/jdk/8u77-b03/jdk-8u77-linux-i586.tar.gz" $ tar -xzvf $ jdk-8u77-linux-x64.tar.gz $ rm -f jdk-8u77-linux-x64.tar.gz
The next step is to create the environment variable called JAVA_HOME
, and to put the Java 8 binaries in the PATH
variable. In Linux, we need to edit the ~/.bashrc
file, and export the variables we need, like in the following:
export JAVA_HOME=~/jdk1.8.0_77 export PATH=$PATH:$JAVA_HOME/bin
Save the file, and then on the same terminal we need to source the file via $ source ~/.bashrc
Now we can test our Java 8 installation. Just type in $ java -version
. You should see something like the following:
$ java -version java version "1.8.0_77" Java(TM) SE Runtime Environment (build 1.8.0_77-b03) Java HotSpot(TM) Server VM (build 25.77-b03, mixed mode)
Let's get started. We will be using the latest Scala version 2.11.8. However, the code inside this book should work with any Scala 2.11.x version. First of all, let's download Scala from http://www.scala-lang.org/.
Scala works on Windows, Mac, and Linux. For this book, I will show how to use Scala on Ubuntu Linux(Debian-based). Open your browser and go to http://www.scala-lang.org/download/.
Download scala 2.11.8
: it will be a TGZ file. Extract it and add it to your path; otherwise, you can use the terminal. Do this as follows:
$ cd ~/ $ wget http://downloads.lightbend.com/scala/2.11.8/scala-2.11.8.tgz $ tar -xzvf scala-2.11.8.tgz $ rm -rf scala-2.11.8.tgz
The next step is to create the environment variable called SCALA_HOME
, and to put the Scala binaries in the PATH
variable. In Linux, we need to edit the ~/.bashrc
file and export the variables we need, like in the following:
export SCALA_HOME=~/scala-2.11.8/ export PATH=$PATH:$SCALA_HOME/bin
Save the file, and then, on the same terminal, we need to source the file via $ source ~/.bashrc
.
Now we can test our Scala installation. Just type in $ scala -version
. You should see something like the following:
$ scala -version Scala code runner version 2.11.8 -- Copyright 2002-2016, LAMP/EPFL
You have successfully installed Java 8 and Scala 2.11. Now we are ready to start learning the FP principles in Scala. For this, we will be using the Scala REPL in the beginning. Scala REPL is bundled with the default Scala installation, and you just need to type $ scala
in your terminal as follows:
$ scala Welcome to Scala 2.11.8 (Java HotSpot(TM) Server VM, Java 1.8.0_77). Type in expressions for evaluation. Or try :help. scala> Scala REPL
Congratulations! You have installed Java 8 and Scala 2.11 successfully.
Read Eval Print and Loop (REPL) is also know as a language shell. Many other languages have shells, like Lisp, Python, and Ruby for instance. The REPL is a simple environment to experiment the language in. It's possible to write very complex programs using REPL, but this is not the REPL goal. Using REPL does not invalidate the usage of an IDE like Eclipse or IntelliJ IDEA. REPL is ideal for testing simple commands and programs without having to spend much time configuring projects like you do with an IDE. The Scala REPL allows you to create a variable, functions, classes, and complex functions as well. There is a history of every command you perform; there is some level of autocomplete too. As a REPL user, you can print variable values and call functions.
Let's get started. Go ahead, open your terminal, and type $ scala
in order to open the Scala REPL. Once the REPL is open, you can just type "Hello World"
. By doing this, you perform two operations: eval and print. The Scala REPL will create a variable called res0
, and store your String there. Then it will print the content of the res0
variable.
We will see how to create Hello World program in Scala REPL as follows:
$ scala Welcome to Scala 2.11.8 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_77). Type in expressions for evaluation. Or try :help. scala> "Hello World" res0: String = Hello World scala>
Scala is a hybrid language, which means it is object-oriented and functional as well. You can create classes and objects in Scala. Next we will create a complete Hello World application using classes.
We will see how to create object-oriented HelloWorld program in Scala REPL as follows:
$ scala Welcome to Scala 2.11.8 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_77). Type in expressions for evaluation. Or try :help. scala> object HelloWorld { | def main(args:Array[String]) = println("Hello World") | } defined object HelloWorld scala> HelloWorld.main(null) Hello World scala>
The first thing you need to realize is that we use the word object instead of class. The Scala language has different constructs compared to Java. Object is a singleton in Scala. It's the same as coding the singleton pattern in Java.
Next we see the word def
that is used in Scala to create functions. In the preceding program, we create the main function similar to the way we do it in Java, and we call the built-in function println
in order to print the String Hello World. Scala imports some Java objects and packages by default. Coding in Scala does not require you to type, for instance, System.out.println("Hello World")
, but you can if you want. Let's take a look at it in the following code:
$ scala Welcome to Scala 2.11.8 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_77). Type in expressions for evaluation. Or try :help. scala> System.out.println("Hello World") Hello World scala>
We can and we will do better. Scala has some abstractions for a console application, so we can write this code with a lesser number of lines of code. To accomplish this goal, we need to extend the Scala class App. When we extend from App, we perform inheritance and we don't need to define the main function. We can just put all the code in the body of the class, which is very convenient and makes the code clean and simple to read.
We will see how to create Scala HelloWorld App in Scala REPL as follows:
$ scala Welcome to Scala 2.11.8 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_77). Type in expressions for evaluation. Or try :help. scala> object HelloWorld extends App { | println("Hello World") | } defined object HelloWorld scala> HelloWorld object HelloWorld scala> HelloWorld.main(null) Hello World scala>
After coding the HelloWorld object in the Scala REPL we can ask the REPL what HelloWorld is, and as you might realize, the REPL will answer that HelloWorld is an object. This is a very convenient Scala way to code console applications, because we can have a Hello World application with just three lines of code. Sadly, to have the same program in Java, it required way more code. Java is a great language for performance, but it is a verbose language compared with Scala, for instance.
We will see how to create Java HelloWorld application as follows:
package scalabook.javacode.chap1; public class HelloWorld { public static void main(String args[]){ System.out.println("Hellow World"); } }
The Java app required six lines of code, while in Scala, we were able to do the same with 50% less code (three lines of code). This is a very simple application. When we are coding complex applications, this difference gets bigger, as a Scala application ends up with way less code than Java.
Remember, we use an object in Scala in order to have a Singleton (Design Pattern that makes sure you have just one instance of a class), and if we want the same in Java, the code would be something like the following:
package scalabook.javacode.chap1; public class HelloWorldSingleton { private HelloWorldSingleton(){} private static class SingletonHelper{ private static final HelloWorldSingleton INSTANCE = new HelloWorldSingleton(); } public static HelloWorldSingleton getInstance(){ return SingletonHelper.INSTANCE; } public void sayHello(){ System.out.println("Hello World"); } public static void main(String[] args) { getInstance().sayHello(); } }
It's not just about the size of the code, but also about consistency and the language providing more abstractions for you. If you write less code, you will have fewer bugs in your software at the end of the day.
Scala is a statically typed language with a very expressive type system which enforces abstractions in a safe yet coherent manner. All values in Scala are Java objects (primitives which are unboxed at runtime), because at the end of the day, Scala runs on the Java JVM. Scala enforces immutability as a core FP principle. This enforcement happens in multiple aspects of the Scala language, for instance, when you create a variable, you do it in an immutable way, when you use an collection, you will use a immutable collection. Scala also lets you use mutable variables and mutable structures, but by design, it favors immutable ones.
When you are coding in Scala, you create variables using the operator var
, or you can use the operator val
. The operator var
allows you to create a mutable state, which is fine as long as you make it local, follow the CORE-FP principles and avoid a mutable shared state.
We will see how to use var in Scala REPL as follows:
$ scala
Welcome to Scala 2.11.8 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_77).
Type in expressions for evaluation. Or try :help.
scala> var x = 10
x: Int = 10
scala> x
res0: Int = 10
scala> x = 11
x: Int = 11
scala> x
res1: Int = 11
scala>
However, Scala has a more interesting construct called val
. Using the val
operator makes your variables immutable, and this means you can't change the value once you've set it. If you try to change the value of the val
variable in Scala, the compiler will give you an error. As a Scala developer, you should use the variable val
as much as possible, because that's a good FP mindset, and it will make your programs better. In Scala, everything is an object; there are no primitives -- the var
and val
rules apply for everything it could but an Int
or String
or even a class.
We will see how to use val in Scala REPL as follows:
$ scala Welcome to Scala 2.11.8 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_77). Type in expressions for evaluation. Or try :help. scala> val x = 10 x: Int = 10 scala> x res0: Int = 10 scala> x = 11 <console>:12: error: reassignment to val x = 11 ^ scala> x res1: Int = 10 scala>
Right now, let's see how we define the most common types in Scala such as Int
, Double
, Boolean
, and String
. Remember, you can create these variables using val
or var
depending on your needs.
We will see Scala variable type in Scala REPL as follows:
$ scala Welcome to Scala 2.11.8 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_77). Type in expressions for evaluation. Or try :help. scala> val x = 10 x: Int = 10 scala> val y = 11.1 y: Double = 11.1 scala> val b = true b: Boolean = true scala> val f = false f: Boolean = false scala> val s = "A Simple String" s: String = A Simple String scala>
For the variables in the preceding code, we did not define the type. Scala language figures it out for us. However, it is possible to specify the type if you want. In Scala, the type comes after the name of the variable.
We will see Scala variables with explicit typing at Scala REPL as follows:
$ scala Welcome to Scala 2.11.8 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_77). Type in expressions for evaluation. Or try :help. scala> val x:Int = 10 x: Int = 10 scala> val y:Double = 11.1 y: Double = 11.1 scala> val s:String = "My String " s: String = "My String " scala> val b:Boolean = true b: Boolean = true scala>
Like any other language, Scala has support for conditional statements like if
and else
. While Java has a switch statement, Scala has a more powerful and functional structure called Pattern Matcher, which we will cover later in this chapter. Scala allows you to use if
statements during variable assignments, which is very practical as well as useful.
We will see how to use if
statements in Scala REPL as follows:
$ scala Welcome to Scala 2.11.8 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_77). Type in expressions for evaluation. Or try :help. scala> val x = 10 x: Int = 10 scala> if (x == 10) | println ("X is 10") X is 10 scala> val y = if (x == 10 ) 11 y: AnyVal = 11 scala> y res1: AnyVal = 11 scala>
In the preceding code, you can see that we set the variable y
based on an if
condition. Scala if
conditions are very powerful, and they also can be used in return statements.
We will see how to use if
statements in return statements in Scala REPL as follows:
$ scala Welcome to Scala 2.11.8 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_77). Type in expressions for evaluation. Or try :help. scala> val x = 10 x: Int = 10 scala> def someFunction = if (x == 10) "X is 10" someFunction: Any scala> someFunction res0: Any = X is 10 scala>
Scala supports else
statements too, and you also can use them in variables and return statements as wellas follows:
~$ scala Welcome to Scala 2.11.8 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_77). Type in expressions for evaluation. Or try :help. scala> val x = 10 x: Int = 10 scala> if (x==10){ | println("X is 10") | } else { | println ("X is something else") | } X is 10 scala>
Now you will learn how to use for loops in Scala. For loops are very powerful in Scala. We will start with the basics and later we will move on to functional loops used for comprehensions, also know as List
comprehensions.
In Scala, for loops work with ranges, which is another Scala data structure that represents numbers from a starting point to an end point. The range is created using the left arrow operator(<-
). Scala allows you to have multiple ranges in the same for loop as long as you use the semicolon(;
).
You also can use if
statements in order to filter data inside for loops, and work smoothly with List
structures. Scala allows you to create variables inside a for loop as well. Right now, let's see some code which illustrates the various for loop usages in Scala language.
We will see how to use basic for loop in Scala REPL as follows:
$ scala Welcome to Scala 2.11.8 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_77). Type in expressions for evaluation. Or try :help. scala> for ( i <- 1 to 10) | println("i * " + i + " = " + i * 10) i * 1 = 10 i * 2 = 20 i * 3 = 30 i * 4 = 40 i * 5 = 50 i * 6 = 60 i * 7 = 70 i * 8 = 80 i * 9 = 90 i * 10 = 100 scala>
Right now, we will create a for loop using a Scala data structure called List
. This is very useful, because in the first line of code, you can define a List
as well as set its values in the same line. Since we are using the List
structure, you don't need to pass any other argument besides the List
itself.
We will see how to use
for with List
in Scala REPL as follows:
$ scala Welcome to Scala 2.11.8 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_77). Type in expressions for evaluation. Or try :help. scala> val listOfValues = List(1,2,3,4,5,6,7,8,9,10) listOfValues: List[Int] = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) scala> for ( i<- listOfValues ) println(i) 1 2 3 4 5 6 7 8 9 10 scala>
Next, we can use for loops with if
statements in order to apply some filtering. Later in this book, we will approach a more functional way to approach filtering using functions. For this code, let's say we want to get just the even numbers on the list and print them.
We will see how to use for
with if
statements in Scala REPL as follows:
$ scala Welcome to Scala 2.11.8 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_77). Type in expressions for evaluation. Or try :help. scala> val listOfValues = List(1,2,3,4,5,6,7,8,9,10) listOfValues: List[Int] = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) scala> for ( i<- listOfValues ) if (i % 2== 0) println(i) 2 4 6 8 10 scala>
In Scala language, we just need two lines of code to perform this filtering, whereas in Java it would have required at least eleven lines of code as you see in the following code:
package scalabook.javacode.chap1; import java.util.Arrays; import java.util.List; public class ForLoopsEvenNumberFiltering { public static void main(String[] args) { List<Integer> listOfValues = Arrays.asList( new Integer[]{1,2,3,4,5,6,7,8,9,10}); for(Integer i : listOfValues){ if (i%2==0) System.out.println(i); } } }
Also known as list or sequence comprehensions, for comprehensions are one of the FP ways to perform loops. This is a language support to create List
structure or collections based on other collections. This task is performed in a SetBuilder
notation. Another way to accomplish the same goal would be by using the Map
and filter
functions, which we will cover later in this chapter. For comprehensions can be used in a generator form, which would introduce new variables and values, or in a reductionist way, which would filter values resulting into a new collection or sequence. The syntax is: for (expt) yield e
, where the yield
operator will add new values to a new collection/sequence that will be created from the original sequence.
We will see how to use for
comprehension in Scala REPL as follows:
$ scala Welcome to Scala 2.11.8 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_77). Type in expressions for evaluation. Or try :help. scala> val names = Set("Diego", "James", "John", "Sam", "Christophe") names: scala.collection.immutable.Set[String] = Set(John, Sam, Diego, James, Christophe) scala> scala> val brazilians = for { | name <- names | initial <- name.substring(0, 1) | } yield if (name.contains("Die")) name brazillians: scala.collection.immutable.Set[Any] = Set((), Diego) scala>
In the preceding code, we create a set of names. As you can see, Scala, by default, prefers immutable data structures and uses immutable.Set
. When we apply the for
loop, we are simply filtering only the names
which contain a specific substring, and then, using the yield
operator, we are creating a new Set
structure. The yield
operator will keep the structure you are using. For instance, if we use List
structure, it would create a List
instead of a Set structure
, the yield
operator will always keep the same data collection you have on the variable. Another interesting aspect of the preceding code is the fact that we are holding the result of the for
comprehension in a variable called Brazilians. Java does not have for
comprehensions, but we could use similar code although it would require way more lines of code.
We will see how to use Java code for performing filtering with collections as follows:
package scalabook.javacode.chap1; import java.util.LinkedHashSet; import java.util.Set; public class JavaNoForComprehension { public static void main(String[] args) { Set<String> names = new LinkedHashSet<>(); Set<String> brazillians = new LinkedHashSet<>(); names.add("Diego"); names.add("James"); names.add("John"); names.add("Sam"); names.add("Christophe"); for (String name: names){ if (name.contains("Die")) brazillians.add(name); } System.out.println(brazillians); } }
In the previous section, we saw how to create the List
and Set
structures in Scala in an immutable way. Now we will learn to work with the List
and Set
structures in a mutable way, and also with other collections such as sequences, tuples, and Maps. Let's take a look at the Scala collections hierarchy tree, as shown in the following diagram:
Now let's take a look at the Scala Seq class hierarchy. As you can see, Seq is traversable as well.
Scala collections extend from traversable, which is the main trait of all collection's descends. List
structures, for instance, extend from Seq class hierarchy, which means sequence - List
is a kind of sequence. All these trees are immutable or mutable depending on the Scala package you end up using.
Let's see how to perform basic mutable operations with List
structures in Scala. In order to have filter and removal operations, we need use a Buffer
sequence as follows:
$ scala Welcome to Scala 2.11.8 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_77). Type in expressions for evaluation. Or try :help. scala> var ms = scala.collection.mutable.ListBuffer(1,2,3) ms: scala.collection.mutable.ListBuffer[Int] = ListBuffer(1, 2, 3) scala> ms += 4 res0: scala.collection.mutable.ListBuffer[Int] = ListBuffer(1, 2, 3, 4) scala> ms += 5 res1: scala.collection.mutable.ListBuffer[Int] = ListBuffer(1, 2, 3, 4, 5) scala> ms += 6 res2: scala.collection.mutable.ListBuffer[Int] = ListBuffer(1, 2, 3, 4, 5, 6) scala> ms(1) res3: Int = 2 scala> ms(5) res4: Int = 6 scala> ms -= 5 res5: scala.collection.mutable.ListBuffer[Int] = ListBuffer(1, 2, 3, 4, 6) scala> ms -= 6 res6: scala.collection.mutable.ListBuffer[Int] = ListBuffer(1, 2, 3, 4) scala>
Let's see the next set of code.
We will see how to create, remove, and get an item from a mutable list in Scala REPL as follows:
$ scala Welcome to Scala 2.11.8 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_77). Type in expressions for evaluation. Or try :help. scala> var names = scala.collection.mutable.SortedSet[String]("Diego", "Poletto", "Jackson") names: scala.collection.mutable.SortedSet[String] = TreeSet(Diego, Jackson, Poletto) scala> names += "Sam" res2: scala.collection.mutable.SortedSet[String] = TreeSet(Diego, Jackson, Poletto, Sam) scala> names("Diego") res4: Boolean = true scala> names -= "Jackson" res5: scala.collection.mutable.SortedSet[String] = TreeSet(Diego, Poletto, Sam) scala>
Have you ever wanted to return multiple values in a method? Well, in Java you have to create a class, but in Scala, there is a more convenient way to perform this task, and you won't need to create new classes each time. Tuples allow you to return or simply hold multiple values in methods without having to create a specific type.
We will see Scala tuples as follows:
$ scala Welcome to Scala 2.11.8 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_77). Type in expressions for evaluation. Or try :help. scala> val config = ("localhost", 8080) config: (String, Int) = (localhost,8080) scala> config._1 res0: String = localhost scala> config._2 res1: Int = 8080 scala>
Scala has special methods called _1
and _2
which you can use to retrieve a tuple's values. The only thing you have to keep in mind is the fact that values are kept in the order of insertion in the tuple.
Scala has a very practical and useful collection library. A Map, for instance, is a key/value pair that can be retrieved based on the key, which is unique. However, Map values do not need to be unique. Like other Scala collections, you have mutable and immutable Map collections. Keep in mind that Scala favors immutable collections over mutable ones.
We will see Scala immutable Map in Scala REPL as follows:
$ scala Welcome to Scala 2.11.8 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_77). Type in expressions for evaluation. Or try :help. scala> val numbers = Map("one" -> 1, | "two" -> 2, | "three" -> 3, | "four" -> 4, | "five" -> 5, | "six" -> 6, | "seven" -> 7, | "eight" -> 8, | "nine" -> 9, | "ten" -> 10) numbers: scala.collection.immutable.Map[String,Int] = Map(four -> 4, three -> 3, two -> 2, six -> 6, seven -> 7, ten -> 10, five -> 5, nine -> 9, one -> 1, eight -> 8) scala> scala> numbers.keys res0: Iterable[String] = Set(four, three, two, six, seven, ten, five, nine, one, eight) scala> scala> numbers.values res1: Iterable[Int] = MapLike(4, 3, 2, 6, 7, 10, 5, 9, 1, 8) scala> scala> numbers("one") res2: Int = 1 scala>
As you can see, Scala uses scala.collection.immutable.Map
when you create a Map using Map()
. Both keys and values are iterable, and you can have access to all the keys with the keys
method or to all the values using the values
method.
We will see Scala mutable Map in Scala REPL as follows:
$ scala Welcome to Scala 2.11.8 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_77). Type in expressions for evaluation. Or try :help. scala> val map = scala.collection.mutable.HashMap.empty[Int,String] map: scala.collection.mutable.HashMap[Int,String] = Map() scala> map += (1 -> "one") res0: map.type = Map(1 -> one) scala> map += (2 -> "two") res1: map.type = Map(2 -> two, 1 -> one) scala> map += (3 -> "three") res2: map.type = Map(2 -> two, 1 -> one, 3 -> three) scala> map += (4 -> "mutable") res3: map.type = Map(2 -> two, 4 -> mutable, 1 -> one, 3 -> three) scala>
If you are dealing with mutable state, you have to be explicit and this is great in Scala, because it increases developers' awareness and avoids mutable shared state by default. So, in order to have a mutable Map, we need to explicitly create the Map with scala.collection.mutable.HashMap
.
Monads are combinable parametrized container types which have support for higher-order functions. Remember higher-order functions are functions which receive functions as parameters and return functions as results. One of the most used functions in FP is Map. Map takes a function, applies it to each element in the container, and returns a new container.
We will see Map function in Scala REPL as follows:
$ scala Welcome to Scala 2.11.8 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_77). Type in expressions for evaluation. Or try :help. scala> scala> val numbers = List(1,2,3,4,5,6) numbers: List[Int] = List(1, 2, 3, 4, 5, 6) scala> def doubleIt(i:Int):Double = i * 2 doubleIt: (i: Int)Double scala> val doubled = numbers.map( doubleIt _ ) doubled: List[Double] = List(2.0, 4.0, 6.0, 8.0, 10.0, 12.0) scala> val doubled = numbers.map( 2.0 * _ ) doubled: List[Int] = List(2.0, 4.0, 6.0, 8.0, 10.0, 12.0) scala>
In the preceding code, we created a list of numbers containing 1,2,3,4,5, and 6. We also defined a Scala function called doubleIt
, which receives an integer and multiplies it by 2.0
resulting in a double number. The map
function calls doubleIt
for each element in the List (1,2,3,4,5,6)
, and the result is a new container, a brand new List
instance containing the new values.
Scala has some syntactical sugar which helps us to be more productive. For instance, you may realize that in the previous code, we also did - 2.0 * _
. The underscore is a special operator for this specific case -- it means the current value is being iterated into the collection. Scala will create a function from this expression for us.
As you might have realized, map
functions are pretty useful for lots of reasons: one reason is that you can do complex computations without using the for
loop explicitly, and this makes your code functional. Secondly, we can use a map
function to convert element types from one type to another. That's what we did in the previous code: we transformed a list of integers into a list of doubles. Look at the following:
scala> val one = Some(1) one: Some[Int] = Some(1) scala> val oneString = one.map(_.toString) oneString: Option[String] = Some(1)
The map
function operates over several data structures and not only collections, as you can see in the previous code. You can use the map
function on pretty much everything in Scala language.
The map
function is great, but you can end up with nested structures. That's why, when we are working with Monads, we use a slightly different version of the map
function called flatMap
, which works in a very similar way to the map
function, but returns the values in a flat form instead of nested values.
In order to have a monad, you need to have a method called flatMap
. Other function languages such as Haskell call flatMap
as bind
, and use the operator >>=
. The syntax changes with the language, but the concept is the same.
Monads can be built in different ways. In Scala, we need a single argument constructor which will work as a monad factory. Basically, the constructor receives one type, A
, and returns Monad[A]
or just M[A]
. For instance, unit(A)
for a List
will be == List[A]
and unit(A)
, where a is an Option == Option[A]
. In Scala, you don't need to have unit; this is optional. To have a monad in Scala, you need to have map and flatMap
implemented.
Working with Monads will make you write a little bit more code than before. However, you will get a way better API, which will be easier to reuse and your potential complexity will be managed, because you won't need to write a complex code full of if
and for loops. The possibilities are expressed through the types, and the compiler can check it for you. Let us see a simple monad example in Scala language:
We will see option Monad in Scala as follows:
$ scala Welcome to Scala 2.11.8 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_77). Type in expressions for evaluation. Or try :help. scala> val a:Option[Int] = Some(1) a: Option[Int] = Some(1) scala> a.get res0: Int = 1 scala> val b:Option[Int] = None b: Option[Int] = None scala> b.get java.util.NoSuchElementException: None.get at scala.None$.get(Option.scala:347) at scala.None$.get(Option.scala:345) ... 32 elided scala> b.getOrElse(0) res2: Int = 0 scala> a == b res3: Boolean = false scala>
In Haskell, this also known as the Maybe monad. Option means optional value, because we are not 100% sure if the value will be present. In order to express a value, we use the Some
type, and in order to express the lack of value, we use none. Option Monads are great, they make your code more explicit, because a method might receive or return an option, which means you are explicitly saying this could be null. However, this technique is not only more expressive but also safer, since you won't get a null pointer, because you have a container around the value. Although, if you call the method get
in Option
and it is none, you will get a NoSuchelementException
. In order to fix this, you can use the method getOrElse
, and you can supply a fallback value which will be used in the case of none. Alright, but you might be wondering where the flatMap
method is. Don't worry, Scala implements this method for us into the Option
abstraction, so you can use it with no issues.
scala> val c = Some("one") c: Some[String] = Some(one) scala> c.flatMap( s => Some(s.toUpperCase) ) res6: Option[String] = Some(ONE)
The Scala REPL can perform autocomplete for you. If you type C + Tab, you will see all the available methods for the Some
class. The map
function is available for you to use, and as I said before, there is no unit function in Scala whatsoever. However, it is not wrong if you add in your APIs.
Following are the list of all methods using the Scala REPL:
$ scala Welcome to Scala 2.11.8 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_77). Type in expressions for evaluation. Or try :help. scala> val c = Some("one") c: Some[String] = Some(one) scala> c. ++ count foreach iterator productArity seq toBuffer unzip ++: drop genericBuilder last productElement size toIndexedSeq unzip3 /: dropRight get lastOption productIterator slice toIterable view :\ dropWhile getOrElse map productPrefix sliding toIterator withFilter WithFilter equals groupBy max reduce span toLeft x addString exists grouped maxBy reduceLeft splitAt toList zip aggregate filter hasDefiniteSize min reduceLeftOption stringPrefix toMap zipAll canEqual filterNot hashCode minBy reduceOption sum toRight zipWithIndex collect find head mkString reduceRight tail toSeq collectFirst flatMap headOption nonEmpty reduceRightOption tails toSet companion flatten init orElse repr take toStream contains fold inits orNull sameElements takeRight toString copy foldLeft isDefined par scan takeWhile toTraversable copyToArray foldRight isEmpty partition scanLeft to toVector copyToBuffer forall isTraversableAgain product scanRight toArray transpose scala> c
As a hybrid post-functional language, Scala allows you to write OO code and create classes as well. Right now we will learn how to create classes and functions inside classes, and also how to work with traits, which are similar to Java interfaces in concept but way more powerful in practice.
We will see a simple Scala class in Scala REPL as follows:
$ scala Welcome to Scala 2.11.8 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_77). Type in expressions for evaluation. Or try :help. scala> class Calculator { | def add(a: Int, b: Int): Int = a + b | def multiply(n: Int, f: Int): Int = n * f | } defined class Calculator scala> scala> val c = new Calculator c: Calculator = Calculator@380fb434 scala> c.add(1,2) res0: Int = 3 scala> c.multiply(3,2) res1: Int = 6 scala>
At first glance, the preceding code looks like Java. But let's add constructors, getters, and setters, and then you can see how much we can accomplish with just a few lines of code.
Following is a Scala plain old Java object in Scala REPL:
$ scala Welcome to Scala 2.11.8 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_77). Type in expressions for evaluation. Or try :help. scala> class Person( | @scala.beans.BeanProperty var name:String = "", | @scala.beans.BeanProperty var age:Int = 0 | ){ | name = name.toUpperCase | override def toString = "name: " + name + " age: " + age | } defined class Person scala> scala> val p = new Person("Diego",31) p: Person = name: DIEGO age: 31 scala> val p1 = new Person(age = 31, name = "Diego") p1: Person = name: DIEGO age: 31 scala> p.getAge res0: Int = 31 scala> p1.getName res1: String = DIEGO scala>
Constructors in Scala are just lines of code. You might realize that we get the name
variable, and apply a function to change the given name to upper case in the preceding example. If you want, you can put as many lines as you want, and you can perform as many computations as you wish.
On this same code, we perform method overriding as well, because we override the toString
method. In Scala, in order to do an override, you need to use the override
operator in front of the function definition.
We just wrote a Plain Old Java Object (POJO) with very few lines of code in Scala. Scala has a special annotation called @scala.beans.BeanProperty
, which generates the getter and setter method for you. This is very useful, and saves lots of lines of code. However, the target needs to be public; you can't a apply BeanProperty
annotation on top of a private var
or val
object.
Following is a Person class in Java:
package scalabook.javacode.chap1; public class JavaPerson { private String name; private Integer age; public JavaPerson() {} public JavaPerson(String name, Integer age) { super(); this.name = name; this.age = age; } public JavaPerson(String name) { super(); this.name = name; } public JavaPerson(Integer age) { super(); this.age = age; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
It's possible to do inheritance in Scala as well. For such a task, you use the operator extend
after the class definition. Scala just allows you to extend one class, just like Java. Java does not allow multiple inheritance like C++. However, Scala allows it by using the Mixing technique with traits. Scala traits are like Java interface, but you can also add concrete code, and you are allowed to have as many traits as you want in your code.
Following is a Scala inheritance code in Scala REPL:
$ scala Welcome to Scala 2.11.8 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_77). Type in expressions for evaluation. Or try :help. scala> class Person( | @scala.beans.BeanProperty var name:String = "", | @scala.beans.BeanProperty var age:Int = 0 | ){ | name = name.toUpperCase | override def toString = "name: " + name + " age: " + age | } defined class Person scala> scala> class LowerCasePerson(name:String,age:Int) extends Person(name,age) { | setName(name.toLowerCase) | } defined class LowerCasePerson scala> scala> val p = new LowerCasePerson("DIEGO PACHECO",31) p: LowerCasePerson = name: diego pacheco age: 31 scala> p.getName res0: String = diego pacheco scala>
Scala does not make constructors inheritance like Java. So you need to rewrite the constructors and pass the values through a super class. All code inside the class will be the secondary constructor. All code inside parentheses ()
in the class definition will be the primary constructor. It's possible to have multiple constructors using the this
operator. For this particular implementation, we changed the default behavior and added new constructor code in order to make the given name lower case, instead of the default uppercase defined by the Person
superclass.
Following is a Scala traits sample code in Scala REPL:
$ scala Welcome to Scala 2.11.8 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_77). Type in expressions for evaluation. Or try :help. scala> trait Car defined trait Car scala> scala> trait SportCar { | val brand:String | def run():String = "Rghhhhh Rghhhhh Rghhhhh...." | } defined trait SportCar scala> scala> trait Printable { | def printIt:Unit | } defined trait Printable scala> scala> class BMW extends Car with SportCar with Printable{ | override val brand = "BMW" | override def printIt:Unit = println(brand + " does " + run() ) | } defined class BMW scala> scala> val x1 = new BMW x1: BMW = BMW@22a71081 scala> x1.printIt BMW does Rghhhhh Rghhhhh Rghhhhh.... scala>
In the preceding code, we created multiple traits. One is called Car, which is the mother trait. Traits support inheritance as well, and we have it with the SportCar
trait which extends from the Car
trait. The SportCar
trait demands a variable called brand, and defines a concrete implementation of the function run. Finally, we have a class called BMW
which extends from multiple traits -- this technique is called mixing.
Following is a Scala traits using variable mixing technique at Scala REPL:
$ scala Welcome to Scala 2.11.8 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_77). Type in expressions for evaluation. Or try :help. scala> trait SportCar { | def run():String = "Rghhhhh Rghhhhh Rghhhhh...." | } defined trait SportCar scala> scala> val bmw = new Object with SportCar bmw: SportCar = $anon$1@ed17bee scala> bmw.run res0: String = Rghhhhh Rghhhhh Rghhhhh.... scala>
Scala is a very powerful language indeed. It's possible to add traits to a variable at runtime. When you define a variable, you can use the with
operator after the assignment. This is a very useful feature, because it makes it easier to make function composition. You can have multiple specialized traits and just add them in your variables as you need them.
Scala allows you to create the type
alias as well, this is a very simple technique which will increase the readability of your code. It's just a simple alias.
Following is a Scala type alias sample in Scala REPL:
$ scala Welcome to Scala 2.11.8 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_77). Type in expressions for evaluation. Or try :help. scala> type Email = String defined type alias Email scala> scala> val e = new Email("me@mail.com.br") e: String = me@mail.com.br scala>
When you are coding with Scala, it is highly recommended that you use the type
alias and traits for everything, because that way you will get more advantages with your compiler, and you will avoid writing unnecessary code and unnecessary unit tests.
We are not done yet in terms of the OO features in Scala; there is another very interesting way to work with classes in Scala: the so-called case classes. Case classes are great because you can have a class with way less number of lines of code and case classes can be part of a Pattern Matcher.
Following is a Scala case classes feature in Scala REPL:
$ scala Welcome to Scala 2.11.8 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_77). Type in expressions for evaluation. Or try :help. scala> case class Person(name: String, age: Int) defined class Person scala> val p = Person("Diego",31) p: Person = Person(Diego,31) scala> val p2 = Person("Diego",32) p2: Person = Person(Diego,32) scala> p.name res0: String = Diego scala> p.age res1: Int = 31 scala> p == p res2: Boolean = true scala> p.toString res3: String = Person(Diego,31) scala> p.hashCode res4: Int = 668670772 scala> p.equals(p2) res5: Boolean = false scala> p.equals(p) res6: Boolean = true scala>
This is the Scala way to work with classes. Because this is so much easier and compact, you pretty much create a class with one line of code, and you can have the equals
and hashcode
methods for free.
When you code in Java, you can use a Switch statement. However, in Scala, we have a more powerful feature called Pattern Matcher, which is a kind of switch but on steroids.
Following is a Simple Pattern Matcher in Scala:
$ scala Welcome to Scala 2.11.8 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_77). Type in expressions for evaluation. Or try :help. scala> def resolve(choice:Int):String = choice match { | case 1 => "yes" | case 0 => "no" | case _ => throw new IllegalArgumentException("Valid arguments are: 0 or 1. Your arg is: " + choice) | } resolve: (choice: Int)String scala> println(resolve(0)) no scala> println(resolve(1)) yes scala> try { | println(resolve(33)) | } catch{ | case e:Exception => println("Something Went Worng. EX: " + e) | } Something Went Worng. EX: java.lang.IllegalArgumentException: Valid arguments are: 0 or 1. Your arg is: 33 scala>
Scala uses Pattern Matcher for error handling. Java does not have Pattern Matcher like Scala. It's similar to a switch statement; however, Pattern Matcher can be used in a method return statement as you can see in the preceding code. Scala developers can specify a special operator called _
(Underscore), which allows you to specify anything in the Pattern Matcher scope. This behavior is similar to else
in an if
conditional. However, in Scala, you can use _
in several places, and not only as the otherwise clause, like in Java switch.
Error handling in Scala is similar to error handling in Java. We use try...catch blocks. The main difference is that you have to use Pattern Matcher in Scala, which is great because it adds more flexibility to your code. Pattern Matcher in Scala can operate against many data structures like case classes, collections, integers, and strings.
The preceding code is pretty simple and straightforward. Next we will see a more complex and advanced code using the Scala Pattern Matcher feature.
Following is an Advanced Pattern Matcher using Scala REPL:
$ scala Welcome to Scala 2.11.8 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_77). Type in expressions for evaluation. Or try :help. scala> def factorial(n:Int):Int = n match { | case 0 => 1 | case n => n * factorial(n - 1) | } factorial: (n: Int)Int scala> scala> println(factorial(3)) 6 scala> println(factorial(6)) 720 scala>
Pattern Matcher can be used in a very functional way. For instance, in the preceding code, we use the Pattern Matcher for recursion. There is no need to create a variable to store the result, we can put the Pattern Matcher straight to the function return, which is very convenient and saves lots of lines of code.
Following is an Advanced complex Pattern Matcher using Scala REPL:
$ scala Welcome to Scala 2.11.8 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_77). Type in expressions for evaluation. Or try :help. scala> trait Color defined trait Color scala> case class Red(saturation: Int) extends Color defined class Red scala> case class Green(saturation: Int) extends Color defined class Green scala> case class Blue(saturation: Int) extends Color defined class Blue scala> def matcher(arg:Any): String = arg match { | case "Scala" => "A Awesome Language" | case x: Int => "An Int with value " + x | case Red(100) => "Red sat 100" | case Red(_) => "Any kind of RED sat" | case Green(s) if s == 233 => "Green sat 233" | case Green(s) => "Green sat " + s | case c: Color => "Some Color: " + c | case w: Any => "Whatever: " + w | } matcher: (arg: Any)String scala> println(matcher("Scala")) A Awesome Language scala> println(matcher(1)) An Int with value 1 scala> println(matcher(Red(100))) Red sat 100 scala> println(matcher(Red(160))) Any kind of RED sat scala> println(matcher(Green(160))) Green sat 160 scala> println(matcher(Green(233))) Green sat 233 scala> println(matcher(Blue(111))) Some Color: Blue(111) scala> println(matcher(false)) Whatever: false scala> println(matcher(new Object)) Whatever: java.lang.Object@b56c222 scala>
The Scala Pattern Matcher is really amazing. We just used an if
statement in the middle of the Pattern Matcher, and also _
to specify a match for any kind of red value. We also used case classes in the middle of the Pattern Matcher expressions.
Partial functions are great for function composition. They can operate with case statements as we just learned from Pattern Matcher. Partial functions are great in the sense of function composition. They allow us to define a function in steps. Scala frameworks and libraries use this feature a lot to create abstractions and callback mechanisms. It's also possible to check if a partial function is being supplied or not.
Partial functions are predictable, because the caller can check beforehand if the value will be applied to the partial function or not. Partial function can be coded with or without case-like statements.
Following is a simple Partial function using Scala REPL:
$ scala Welcome to Scala 2.11.8 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_77). Type in expressions for evaluation. Or try :help. scala> val positiveNumber = new PartialFunction[Int, Int] { | def apply(n:Int) = n / n | def isDefinedAt(n:Int) = n != 0 | } positiveNumber: PartialFunction[Int,Int] = <function1> scala> scala> println( positiveNumber.isDefinedAt(6) ) true scala> println( positiveNumber.isDefinedAt(0) ) false scala> scala> println( positiveNumber(6) ) 1 scala> println( positiveNumber(0) ) java.lang.ArithmeticException: / by zero at $anon$1.apply$mcII$sp(<console>:12) ... 32 elided scala>
Partial functions are Scala classes. They have some methods you need to provide, for instance, apply
and isDefinedAt
. The function isDefinedAt
is used by the caller to check if the PartialFunction
will accept and operate with the value supplied. The apply
function will do the work when the PartialFunction
is executed by Scala.
Following is a Scala PartialFunction without OO using case statements in Scala REPL:
$ scala Welcome to Scala 2.11.8 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_77). Type in expressions for evaluation. Or try :help. scala> val positiveNumber:PartialFunction[Int, Int] = { | case n: Int if n != 0 => n / n | } positiveNumber: PartialFunction[Int,Int] = <function1> scala> scala> println( positiveNumber.isDefinedAt(6) ) true scala> println( positiveNumber.isDefinedAt(0) ) false scala> scala> println( positiveNumber(6) ) 1 scala> println( positiveNumber(0) ) scala.MatchError: 0 (of class java.lang.Integer) at scala.PartialFunction$$anon$1.apply(PartialFunction.scala:253) at scala.PartialFunction$$anon$1.apply(PartialFunction.scala:251) at $anonfun$1.applyOrElse(<console>:11) at $anonfun$1.applyOrElse(<console>:11) at scala.runtime.AbstractPartialFunction$mcII$sp.apply$mcII$sp (AbstractPartialFunction.scala:36) ... 32 elided scala>
Scala was a more fluent way to work with PartialFunction
using the case
statements. When you use the case
statements, you don't need to supply the apply
and isDefinedAt
functions, since the Pattern Matcher takes care of that.
Following is a PartialFunction composition in Scala REPL:
$ scala Welcome to Scala 2.11.8 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_77). Type in expressions for evaluation. Or try :help. scala> val even:PartialFunction[Int, String] = { | case i if i%2 == 0 => "even" | } even: PartialFunction[Int,String] = <function1> scala> scala> val odd:PartialFunction[Int, String] = { case _ => "odd"} odd: PartialFunction[Int,String] = <function1> scala> scala> val evenOrOdd:(Int => String) = even orElse odd evenOrOdd: Int => String = <function1> scala> scala> println( evenOrOdd(1) == "odd" ) true scala> println( evenOrOdd(2) == "even" ) true scala>
Scala allows us to compose as many PartialFunctions
as we want. PartialFunction
composition happens with the orElse
function. In the preceding code, we defined an immutable variable called even
, which verifies even numbers. Secondly, we created a second immutable variable called odd
, which checks for odd numbers. Then we did the composition, and created a third PartialFunction
called evenOrOdd
with compose even and odd using the orElse
operator.
Scala has packages like Java. However, Scala packages are also objects, and you can have code inside a package. Java does not have the same power as Scala in terms of packages. If you add code to a package, it will be available to all classes and functions within that package.
Your package.scala
file should contain the following code
package com.packait.scala.book package object commons { val PI = 3.1415926 object constraintsHolder { val ODD = "Odd" val EVEN = "Even" } def isOdd(n:Int):String = if (n%2==0) constraintsHolder.ODD else null def isEven(n:Int):String = if (n%2!=0) constraintsHolder.EVEN else null def show(s:String) = println(s) }
This is the Scala package object. There is this special token called package
object which you use to define common code to all classes, objects, and functions that are defined inside this package or sub-package. For this case, we define a value of PI as a constant and also one object holder containing the String values for Odd
and Even
. There are also three helper functions, which can and will be used by the classes inside this package.
Your MainApp.scala
file should contain the following code
package com.packait.scala.book.commons object MainApp extends App { show("PI is: " + PI) show(constraintsHolder.getClass.toString()) show( isOdd(2) ) show( isOdd(6) ) show( isEven(3) ) show( isEven(7) ) }
As you can see in the preceding code, this new object is placed in the package: com.packait.scala.book.commons
. Another interesting thing is the fact that we don't have any import statement here because of the package
object feature. When you compile and run this program, you will see the following output:
PI is: 3.1415926 class com.packait.scala.book.commons.package$constraintsHolder$ Odd Odd Even Even
Scala uses the Package
object a great deal providing lots of shortcuts and convenience for all Scala developers. The following is the Scala package
object definition:
/* __ *\ ** ________ ___ / / ___ Scala API ** ** / __/ __// _ | / / / _ | (c) 2003-2013, LAMP/EPFL ** ** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ ** ** /____/\___/_/ |_/____/_/ | | ** ** |/ ** \* */ /** * Core Scala types. They are always available without an explicit import. * @contentDiagram hideNodes "scala.Serializable" */ package object scala { type Throwable = java.lang.Throwable type Exception = java.lang.Exception type Error = java.lang.Error type RuntimeException = java.lang.RuntimeException type NullPointerException = java.lang.NullPointerException type ClassCastException = java.lang.ClassCastException type IndexOutOfBoundsException = java.lang.IndexOutOfBoundsException type ArrayIndexOutOfBoundsException = java.lang.ArrayIndexOutOfBoundsException type StringIndexOutOfBoundsException = java.lang.StringIndexOutOfBoundsException type UnsupportedOperationException = java.lang.UnsupportedOperationException type IllegalArgumentException = java.lang.IllegalArgumentException type NoSuchElementException = java.util.NoSuchElementException type NumberFormatException = java.lang.NumberFormatException type AbstractMethodError = java.lang.AbstractMethodError type InterruptedException = java.lang.InterruptedException // A dummy used by the specialization annotation. val AnyRef = new Specializable { override def toString = "object AnyRef" } type TraversableOnce[+A] = scala.collection.TraversableOnce[A] type Traversable[+A] = scala.collection.Traversable[A] val Traversable = scala.collection.Traversable type Iterable[+A] = scala.collection.Iterable[A] val Iterable = scala.collection.Iterable type Seq[+A] = scala.collection.Seq[A] val Seq = scala.collection.Seq type IndexedSeq[+A] = scala.collection.IndexedSeq[A] val IndexedSeq = scala.collection.IndexedSeq type Iterator[+A] = scala.collection.Iterator[A] val Iterator = scala.collection.Iterator type BufferedIterator[+A] = scala.collection.BufferedIterator[A] type List[+A] = scala.collection.immutable.List[A] val List = scala.collection.immutable.List val Nil = scala.collection.immutable.Nil type ::[A] = scala.collection.immutable.::[A] val :: = scala.collection.immutable.:: val +: = scala.collection.+: val :+ = scala.collection.:+ type Stream[+A] = scala.collection.immutable.Stream[A] val Stream = scala.collection.immutable.Stream val #:: = scala.collection.immutable.Stream.#:: type Vector[+A] = scala.collection.immutable.Vector[A] val Vector = scala.collection.immutable.Vector type StringBuilder = scala.collection.mutable.StringBuilder val StringBuilder = scala.collection.mutable.StringBuilder type Range = scala.collection.immutable.Range val Range = scala.collection.immutable.Range // Numeric types which were moved into scala.math.* type BigDecimal = scala.math.BigDecimal val BigDecimal = scala.math.BigDecimal type BigInt = scala.math.BigInt val BigInt = scala.math.BigInt type Equiv[T] = scala.math.Equiv[T] val Equiv = scala.math.Equiv type Fractional[T] = scala.math.Fractional[T] val Fractional = scala.math.Fractional type Integral[T] = scala.math.Integral[T] val Integral = scala.math.Integral type Numeric[T] = scala.math.Numeric[T] val Numeric = scala.math.Numeric type Ordered[T] = scala.math.Ordered[T] val Ordered = scala.math.Ordered type Ordering[T] = scala.math.Ordering[T] val Ordering = scala.math.Ordering type PartialOrdering[T] = scala.math.PartialOrdering[T] type PartiallyOrdered[T] = scala.math.PartiallyOrdered[T] type Either[+A, +B] = scala.util.Either[A, B] val Either = scala.util.Either type Left[+A, +B] = scala.util.Left[A, B] val Left = scala.util.Left type Right[+A, +B] = scala.util.Right[A, B] val Right = scala.util.Right // Annotations which we might move to annotation.* /* type SerialVersionUID = annotation.SerialVersionUID type deprecated = annotation.deprecated type deprecatedName = annotation.deprecatedName type inline = annotation.inline type native = annotation.native type noinline = annotation.noinline type remote = annotation.remote type specialized = annotation.specialized type transient = annotation.transient type throws = annotation.throws type unchecked = annotation.unchecked.unchecked type volatile = annotation.volatile */ }
Like any great FP language, Scala has lots of built-in functions. These functions make our code more fluent and functional; now it's time to learn some of these functions:
$ scala Welcome to Scala 2.11.8 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_77). Type in expressions for evaluation. Or try :help. scala> // Creates the numbers 1,2,3,4,5 and them multiply they by 2 and creates a new Vector scala> println ((1 to 5).map(_*2)) Vector(2, 4, 6, 8, 10) scala> scala> // Creates 1,2,3 and sum them all with each orher and return the total scala> println ( (1 to 3).reduceLeft(_+_) ) 6 scala> scala> // Creates 1,2,3 and multiply each number by it self and return a Vector scala> println ( (1 to 3).map( x=> x*x ) ) Vector(1, 4, 9) scala> scala> // Creates numbers 1,2,3,4 ans 5 filter only Odd numbers them multiply them odds by 2 and return a Vector scala> println ( (1 to 5) filter { _%2 == 0 } map { _*2 } ) Vector(4, 8) scala> scala> // Creates a List with 1 to 5 and them print each element being multiplyed by 2 scala> List(1,2,3,4,5).foreach ( (i:Int) => println(i * 2 ) ) 2 4 6 8 10 scala> scala> // Creates a List with 1 to 5 and then print each element being multiplied by 2 scala> List(1,2,3,4,5).foreach ( i => println(i * 2) ) 2 4 6 8 10 scala> scala> // Drops 3 elements from the lists scala> println( List(2,3,4,5,6).drop(3)) List(5, 6) scala> println( List(2,3,4,5,6) drop 3 ) List(5, 6) scala> scala> // Zip 2 lists into a single one: It will take 1 element of each list and create a pair List scala> println( List(1,2,3,4).zip( List(6,7,8) )) List((1,6), (2,7), (3,8)) scala> scala> // Take nested lists and create a single list with flat elements scala> println( List(List(1, 2), List(3, 4)).flatten ) List(1, 2, 3, 4) scala> scala> // Finds a person in a List by Age scala> case class Person(age:Int,name:String) defined class Person scala> println( List(Person(31,"Diego"),Person(40,"Nilseu")).find( (p:Person) => p.age <= 33 ) ) Some(Person(31,Diego)) scala>
In Scala, the underscore(_
) means different things in different contexts. The underscore can be used to partially apply a function. It means a value will be supplied later. This feature is useful for function composition and allows you to reuse functions. Let's see some code.
Following is an example using Partial function in Scala REPL:
$ scala Welcome to Scala 2.11.8 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_77). Type in expressions for evaluation. Or try :help. scala> def sum(a:Int,b:Int) = a+b sum: (a: Int, b: Int)Int scala> scala> val add6 = sum(6,_:Int) add6: Int => Int = <function1> scala> scala> println(add6(1)) 7 scala>
In the preceding code, first, we define a function called sum
, which takes two Int
parameters and calculates a sum of these two parameters. Later, we define a function and hold it as a variable called add6
. For the add6
function definition, we just call the sum function passing 6
and _
. Scala will get the parameter passed through add6
, and pass it through the sum
function.
This feature is very popular in function languages like Haskell. Curried functions are similar to partial applications, because they allow some arguments to pass now and others later. However, they are a little bit different.
Following is an example using curried function in Scala REPL:
$ scala Welcome to Scala 2.11.8 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_77). Type in expressions for evaluation. Or try :help. scala> // Function Definition scala> def sum(x:Int)(y:Int):Int = x+y sum: (x: Int)(y: Int)Int scala> scala> // Function call - Calling a curried function scala> sum(2)(3) res0: Int = 5 scala> scala> // Doing partial with Curried functions scala> val add3 = sum(3) _ add3: Int => Int = <function1> scala> scala> // Supply the last argument now scala> add3(3) res1: Int = 6 scala>
For the preceding code, we create a curried function in the function definition. Scala allows us to transform regular/normal functions into curried functions. The following code shows the usage of the curried
function.
Following is an example using curried transformation in Scala REPL:
$ scala Welcome to Scala 2.11.8 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_77). Type in expressions for evaluation. Or try :help. scala> def normalSum(x:Int,y:Int):Int=x+y normalSum: (x: Int, y: Int)Int scala> scala> val curriedSum = (normalSum _).curried curriedSum: Int => (Int => Int) = <function1> scala> scala> val add3= curriedSum(3) add3: Int => Int = <function1> scala> scala> println(add3(3)) 6 scala>
Like C++, Scala permits operator overload. This feature is great for creating custom Domain Specific Languages (DSL), which can be useful to create better software abstractions or even internal or external APIs for developers, or for business people. You should use this feature with wisdom -- imagine if all frameworks decide to overload the same operators with implicits! You might run into trouble. Scala is a very flexible language compared to Java. However, you need to be careful, otherwise you could create code that's hard to maintain or even incompatible with other Scala applications, libraries, or functions.
Following is an example using Scala operator overloading in Scala REPL:
$ scala Welcome to Scala 2.11.8 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_77). Type in expressions for evaluation. Or try :help. scala> case class MyNumber(value:Int){ | def +(that:MyNumber):MyNumber = new MyNumber(that.value + this.value) | def +(that:Int):MyNumber = new MyNumber(that + this.value) | } defined class MyNumber scala> val v = new MyNumber(5) v: MyNumber = MyNumber(5) scala> scala> println(v) MyNumber(5) scala> println(v + v) MyNumber(10) scala> println(v + new MyNumber(4)) MyNumber(9) scala> println(v + 8) MyNumber(13) scala>
As you can see, we have two functions called +
. One of this functions receives a MyNumber
case class, and the other receives a Int
value. You can use OO in Scala with regular classes and functions as well if you wish. We're also favoring immutability here because we always create a new instance of MyNumber
when the operation +
happens.
Implicits allow you to do magic in Scala. With great power comes great responsibility. Implicits allow to you create very powerful DSL, but they also allow you to get crazy, so do it with wisdom. You are allowed to have implicit functions, classes, and objects. The Scala language and other core frameworks from the Scala ecosystem like Akka and PlayFramework use implicits many times.
$ scala Welcome to Scala 2.11.8 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_77). Type in expressions for evaluation. Or try :help. scala> implicit def transformStringtoInt(n:String) = n.toInt warning: there was one feature warning; re-run with -feature for details transformStringtoInt: (n: String)Int scala> scala> val s:String = "123456" s: String = 123456 scala> println(s) 123456 scala> scala> val i:Int = s i: Int = 123456 scala> println(i) 123456 scala>
To use implicits, you need to use the keyword implicit
before a function. Scala will implicitly call that function when it is appropriate. For this case, it will call to convert the String
type to Int
type as we can see.
$ scala Welcome to Scala 2.11.8 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_77). Type in expressions for evaluation. Or try :help. scala> implicit val yValue:Int = 6 yValue: Int = 6 scala> def sum(x:Int)(implicit yValue:Int) = x + yValue sum: (x: Int)(implicit yValue: Int)Int scala> val result = sum(10) result: Int = 16 scala> println(result) 16 scala>
For this other case, given in the last code, we use an implicit parameter in the function sum
. We also used a curried function here. We defined the implicit
function first, and then called the sum
function. This technique is good for externalized functions configuration and values you would let it hard code. It also saves lines of code, because you don't need to pass a parameter to all functions all the time, so it's quite handy.
Futures enable an efficient way to write parallel operations in a nonblocking IO fashion. Futures are placeholder objects for values that might not exist yet. Futures are composable, and they work with callbacks instead of traditional blocking code.
$ scala Welcome to Scala 2.11.8 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_77). Type in expressions for evaluation. Or try :help. scala> import concurrent.Future import concurrent.Future scala> import concurrent.ExecutionContext.Implicits.global import concurrent.ExecutionContext.Implicits.global scala> scala> val f: Future[String] = Future { "Hello world!" } f: scala.concurrent.Future[String] = Success(Hello world!) scala> scala> println("Result: " + f.value.get.get) Result: Hello world! scala> scala> println("Result: " + f) Result: Success(Hello world!) scala>
In order to work with futures in Scala, we have to import concurrent.Future
. We also need an executor, which is a way to work with threads. Scala has a default set of execution services. You can tweak it if you like, however, for now we can just use the defaults; to do that, we just import concurrent.ExecutionContext.Implicits.global
.
It's possible to retrieve the Future
value. Scala has a very explicit API, which makes the developer's life easier, and also gives good samples for how we should code our own APIs. Future has a method called value
, which returns Option[scala.util.Try[A]]
where A
is the generic type you are using for the future; for our case, it's a String A
. Try
is a different way to do a try...catch, and this is safer, because the caller knows beforehand that the code they are calling may fail. Try[Optional]
means that Scala will try to run some code and the code may fail -- even if it does not fail, you might receive None
or Some
. This type of system makes everybody's lives better, because you can have Some
or None
as the Option return. Futures are a kind of callback. For our previous sample code, the result was obtained quite quickly, however, we often use futures to call external APIs, REST services, Microservices, SOAP Webservices, or any code that takes time to run and might not get completed. Futures also work with Pattern Matcher. Let's see another sample code.
$ scala Welcome to Scala 2.11.8 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_77). Type in expressions for evaluation. Or try :help. scala> import concurrent.Future import concurrent.Future scala> import concurrent.ExecutionContext.Implicits.global import concurrent.ExecutionContext.Implicits.global scala> import scala.util.{Success, Failure} import scala.util.{Success, Failure} scala> def createFuture():Future[Int] = { | Future { | val r = scala.util.Random | if (r.nextInt(100)%2==0) 0 else throw new RuntimeException("ODD numbers are not good here :( ") | } | } createFuture: ()scala.concurrent.Future[Int] scala> def evaluateFuture(f:Future[_]) { | f.onComplete { | case Success(i) => println(s"A Success $i ") | case Failure(e) => println(s"Something went wrong. Ex: ${e.getMessage}") | } | } evaluateFuture: (f: scala.concurrent.Future[_])Unit scala> evaluateFuture(createFuture) scala> Something went wrong. Ex: ODD numbers are not good here :( evaluateFuture(createFuture) A Success 0 scala> evaluateFuture(createFuture) Something went wrong. Ex: ODD numbers are not good here :( scala> evaluateFuture(createFuture) Something went wrong. Ex: ODD numbers are not good here :( scala> evaluateFuture(createFuture) A Success 0 scala> evaluateFuture(createFuture) A Success 0 scala>
There is a function called createFuture
, which creates Future[Int]
each time you call it. In the preceding code, we use scala.util.Random
to generate random numbers between 0 and 99. If the number is even, we return a 0
, which means success. However, if the number is odd, we return a RuntimeException
, which will mean a failure.
There is a second function called evaluateFuture
, which receives any Future. We allow a result of any kind of generic parameterized type of function, because we used the magic underscore _
. Then we apply Pattern Matcher with two case classes: Success
and Failure
. In both the cases, we just print on stdin
. We also use another interesting and handy Scala feature called String interpolation. We need to we start the String with s
before ""
. This allows us to use expressions with $
and ${}
to evaluate any variable in the context. This is a different approach for String concatenation from what we have done so far. Later, we made 6
calls for the evaluteFuture
function, passing a new Future each time, created by the function createFuture
.
Reactive programming is better, scalable, and a faster way to build applications. Reactive Programing can be done with OO languages, however, they make a lot of sense with FP languages. When FP is married to Reactive Programing, we get something called Functional Reactive Programing (FRP). Scala FRP can be used for many purposes like GUI, Robotics, and Music, because it gives you a better model to model time. Reactive programming is a new technique, which works with Streams(also known as Data Flows). Streams is a way to think and code applications in a way which can express data transformations and flow. The main idea is to propagate changes through a circuit or flow. Yes, we are talking about a new way to do async programming.
The main library for Reactive Programing is called Reactive Extensions (Rx) - http://reactivex.io/), originally built for .NET by Eric Meijer. It combines the best ideas from the Observer and Iterator Patterns, and FP. Rx has implementations for many languages like Scala, Java, Python, .NET, PHP, and others (https://github.com/ReactiveX). Coding with Rx is easy, and you can create Streams, combine with query-like operators, and also listen (subscribe) to any observable Streams to perform data transformations. Rx is used by many successful companies today like Netflix, GitHub, Microsoft, SoundCloud, Couchbase, Airbnb, Trello, and several others. In this book, we will use RxScala, which is the Scala implementation of the Reactive Streams.
The following table shows the main class/concepts you need to know in order to work with Rx.
Term / Class |
Concept |
Observable |
Create async composable Streams from sources. |
Observer |
A callback function type. |
Subscription |
The bound between the Subscriber and the Observable. Receives notifications from Observables. |
Reactive Streams is also the name of a common specification trying to consolidate and standardize the reactive stream processing, There are several implementations such as RxJava/RxScala, Reactor, Akka, Slick, and Vert.x. You can find more at https://github.com/reactive-streams/reactive-streams-jvm.
Back to the Observables -- we can perform all kinds of operations with observables. For instance, we can filter, select, aggregate, compose, perform time-based operations, and apply backpressure. There are two big wins with Observables instead of callbacks. First of all, Observables are not opinionated about how low-level I/O and threading happens, and secondly, when you are doing complex code, callbacks tend to be nested, and that is when things get ugly and hard to read. Observables have a simple way to do composition thanks to FP.
Observables push values to consumers whenever values are available, which is great because then the values can arrive in sync or async fashion. Rx provides a series of collection operators to do all sorts of data transformations you may need. Let's see some code now. We will use RxScala version 0.26.1, which is compatible with RxJava version 1.1.1+. RxScala is just a wrapper for RxJava (Created by Netflix). Why not use RxJava straight? Because the syntax won't be pleasant; with RxScala, we can have a fluent Scala experience. RxJava is great, however, Java syntax for this is not pleasant - as Scala is, in fact, pretty ugly.
package scalabook.rx.chap1 import rx.lang.scala.Observable import scala.concurrent.duration._ object SimpleRX extends App { val o = Observable. interval(100 millis). take(5) o.subscribe( x => println(s"Got it: $x") ) Thread.sleep(1000) Observable. just(1, 2, 3, 4). reduce(_+_). subscribe( r => println(s"Sum 1,2,3,4 is $r in a Rx Way")) }
If you run this preceding Scala program, you will see the following output:
Got it: 0 Got it: 1 Got it: 2 Got it: 3 Got it: 4 Sum 1,2,3,4 is 10 in a Rx Way
If you try to run this code in the Scala REPL, it will fail, because we need the RxScala and RxJava dependencies. For this, we will need SBT and dependency management. Do not worry, we will cover how to work with SBT in our Scala application in the next chapter.
Going back to the observables, we need to import the Scala Observable. Make sure you get it from the Scala package, because if you get the Java one, you will have issues: in the very first part of the code, we will get numbers starting from 0 each 100 milliseconds, and this code would run forever. To avoid this, we use the take function to put a limit into the collection, so we will get the first five values. Then, later, we subscribe to the observer, and when data is ready, our code will run. For the first sample, it's pretty easy, we are just printing the values we have got. There is a thread sleep in this program, otherwise, the program would terminate, and you would not see any value on the console.
The second part of the code does something more interesting. First of all, it creates an Observable from a static list of values, which are 1,2,3, and 4. We apply a reduce function into the elements, which will sum all the elements with each other, and then we subscribe and print the result.
package scalabook.rx.chap1 import rx.lang.scala.Observable object ComplexRxScala extends App { Observable. just(1,2,3,4,5,6,7,8,9,10). // 1,2,3,4,5,6,7,8,9,10 filter( x => x%2==0). // 2,4,6,8,10 take(2). // 2,4 reduce(_+_). // 6 subscribe( r => println(s"#1 $r")) val o1 = Observable. just(1,2,3,4,5,6,7,8,9,10). // 1,2,3,4,5,6,7,8,9,10 filter( x => x%2==0). // 2, 4, 6, 8, 10 take(3). // 2, 4 ,6 map( n => n * n) // 4, 16, 36 val o2 = Observable. just(1,2,3,4,5,6,7,8,9,10). // 1,2,3,4,5,6,7,8,9,10 filter( x => x%2!=0). // 1, 3, 5, 7, 9 take(3). // 1, 3, 5 map( n => n * n) // 1, 9, 25 val o3 = o1. merge(o2). // 2,16, 36, 1, 9, 25 subscribe( r => println(s"#2 $r")) }
The preceding first part of the code creates an Observable with numbers from 1 to 10, and then applies a filter
function, which will get only the even numbers. It then reduces them, calculates their sum, and lastly, prints the solution. You can visualize it as depicted in the following image:
For the second part of the code, we create two different observables. The first one is with even numbers and the second one is with odd numbers. These two observables are decoupled from each other; you can control as many observables you want. Later on, the code uses a merge function to join these two observables into a third and new observable containing the content of the first and second observables.
There are many functions and options, and you can see the whole list at http://rxmarbles.com/ and https://github.com/ReactiveX/RxScala. For the sake of simplicity, for now, we are just working with numbers. Later, we will use this to do more advance compositions including database calls and external web services calls.
In this chapter, we learned the basic concepts of FP, Reactive Programing, and the Scala language. We learned about the basic constructs of the Scala language and Functional Programming, functions, collections, and OO in Scala, and concurrent programming with Futures.
Next, we will see how to use SBT to build Scala projects. We will learn how to compile and run Scala applications using SBT.