Functional and OOP Programming in Scala
Table of Contents
- 1. Scala
- 1.1. Overview
- 1.2. Tooling
- 1.3. Basic Syntax
- 1.4. Functions
- 1.4.1. Function Definition
- 1.4.2. Anonymous Functions / Lambda Functions or Function Literals
- 1.4.3. Curried Functions
- 1.4.4. Closures
- 1.4.5. Nested Functions
- 1.4.6. Function Composition
- 1.4.7. Higher Order Functions
- 1.4.8. Polymorphic Functions
- 1.4.9. Storing Functions in Data Structures
- 1.4.10. Variadic - function or functions with variable number of arguments:
- 1.4.11. Functions with default parameters
- 1.4.12. Functions with call-by-name parameters
- 1.4.13. Singleton or Module
- 1.5. Imperative Constructs
- 1.6. String Manipulation
- 1.7. Collections
- 1.8. Case classes and pattern matching
- 1.9. Pattern Matching
- 1.10. Option type
- 1.11. Exception Handling with try-catch
- 1.12. Exception Handling with Try (scala.utils.Try)
- 1.13. For comprehensions and Monads
- 1.14. OOP - Object Oriented Programming
- 1.15. Scala Type System
- 1.16. Scala Implicit
- 1.17. OO - Design Patterns
- 2. Bookmarks and Resources
1 Scala
1.1 Overview
1.1.1 Features
Scala is a statically typed, functional, object oriented and imperative language created by Martin Odersky , or multi-paradigm programming language that runs in JVM - Java Virtual Machine that allows type-safe programming with high productivity.
Features:
- Interactive - Scala REPL allows the user to perform exploratory design and learn and experiment the Java API interactively.
- Scripting language - Scala has a fast initialization time what makes it suitable for scripting and small proof-concept programs.
- Java Integration - As a JVM language Scala allows reusing of all Java libraries and integration with old code bases.
- Multi Paradigm:
- Imperative
- Object Oriented
- Functional
- Functional Programming Features
- Lambda function (aka anonymous function)
- Provides short syntax for curried functions
- Higher order methods. Functions can be passed as arguments to methods.
- Type Inference. It frees the developer from declaring typing for all values and functions in a similar way to Haskell, although Scala requires some type anotations.
- Algebraic Data Types. AGDT is useful for represeting AST - Abstract Syntax Trees; write parsers and compilers and perform symbolic computations.
- Pattern Matching
- Tail Call Optimization. TCO makes tail-recursive functions be equivalent to a while loop, hence not consuming stack frames avoiding stack overflows.
- Rich Collection Library - Collections are standard data structures such as lists, arrays, vectors, hash maps (hash tables, aka dictionaries), tuples and so on with a wide variety of performance guarantees. The collection library provides lots of higher order methods (methods which takes functions as arguments) such as .map, .foreach, .flatMap, .take(n) …
- Interoperability between OOP and FP - There are lots features and syntax sugars in Scala which makes the integration between OOP and FP smoothly, such as syntax sugars for calling methods, the ability to pass functions and method as arguments of methods or functions and the collection library.
- Object Oriented Features
- Pure Object Oriented language - Everything in Scala is an object, there is no primitive types like in Java and all objects can be extended or get extension methods.
- Better interfaces: Unlike, Java interfaces, Scala traits allows concrete methods.
- Drawbacks - As everything in the real world, there is always trade-offs.
- Slower compilation time than Java.
- Scala generated code is not binary compatible between Scala releases, for instance, a library compiled againt Scala 2.11 may not run when compiled against version 2.12.
- No convenient syntax sugar for function application which avoids parenthesis, for instance the expression f1(f2(f3(x))) can be written as f1 $ f2 $ f3 $ x in Haskell and as f1 <| f2 <| f3 <| x in F# or using reverse function application as x |> f1 |> f2 |> f3.
- While it is easy to call Java code from Scala, the inverse is harder due to Scala advanced type system.
Notes:
- Scala may also be convenient as first programming language since it provides object oriented programming and functional programming support and also provides access to the whole Java API. The scala REPL (Read-Eval-Print-Loop) also makes experimentation and exploratory design easier.
1.1.2 Getting Scala
1.1.3 File Extensions
Extension | Description |
---|---|
.scala | Scala source code or script |
.java | Java source code |
.class | Java bytecode or compile code |
.jar | Java application |
1.2 Tooling
1.2.1 Overview
Program | Description |
---|---|
Scala tools | |
scala | Scala REPL or interactive shell like python or ipython. |
scalac | Scala compiler. |
scaladoc | Documentation builder similar to Javadoc. |
sbt | Simple Building Tool. Or scala building tool similar to Maven, but less verbose. |
Java tools | |
java | Java runtime. |
javac | Java compiler. |
javadoc | java documentation builder. |
Other Tools:
- Scala SBT - SBT Building Tool
- Ammonite REPL - Ammonite REPL is an enhanced Scala REPL with syntax highlighting, multi-line editing and more.
- Proguard - Tool for shrinking, optmizing and obfuscating Java Applications. It also can reduce the size of jar files and make the initialization time faster.
- Launch4j - Cross-platform Java executable wrapper
- IDEs
- Eclipse
- Intelij
1.2.2 REPL - Scala Shell
1.2.2.1 REPL Commands
The Scala REPL or Scala shell allows exploratory design and interactive lerarning about the Java API.
Command | Description |
---|---|
:help | Show help |
:paste | Paste a block of code and enter Ctrl+D. |
:paste <path-to-file> |
Load a file. |
:load <path-to-file> |
Load a Scala file into the repl. |
:require <path-to-*.jar> |
Load a Jar file into the REPL. |
:history | Show command history |
:reset | Reset repl to its initial state. |
:silent | Enable/disable automatic printing of results. |
:quit or Ctrl + c | Exit REPL |
Scala REPL:
Example: Load a scala script in the repl. File: src/clockDisplayGui.scala
scala> :load clockDisplayGui.scala Loading clockDisplayGui.scala... runTimer: (interval: Int, taskFn: () => Unit)java.util.Timer currentTime: ()String frame: javax.swing.JFrame = javax.swing.JFrame[frame0,0,0,0x0,invalid,hidden,layout=java.awt.BorderLayout,title=Java Clock App,resizable,normal,defaultCloseOperation=HIDE_ON_CLOSE,rootPane=javax.swing.JRootPane[,0,0,0x0,invalid,layout=javax.swing.JRootPane$RootLayout,alignmentX=0.0,alignmentY=0.0,border=,flags=16777673,maximumSize=,minimumSize=,preferredSize=],rootPaneCheckingEnabled=true] label: javax.swing.JLabel = javax.swing.JLabel[,0,0,0x0,invalid,alignmentX=0.0,alignmentY=0.0,border=,flags=8388608,maximumSize=,minimumSize=,preferredSize=,defaultIcon=,disabledIcon=,horizontalAlignment=LEADING,horizontalTextPosition=TRAILING,iconTextGap=4,labelFor=,text=,verticalAlignment=CENTER,verticalTextPosition=CENTER] res0: java.awt.Component = javax.swing.JLabel[,0,0,0x0,invalid,alignmentX=0.0,alignmentY=0.0,border=,flags=8388608,maximumSize=,minimumSize=,preferredSize=,defaultIcon=,disabledIcon=,horizontalAlignment=LEADING,horizontalTextPosition=TRAILING,iconTextGap=4,labelFor=,text=,verticalAlignment=CENTER,verticalTextPosition=CENTER] res3: java.util.Timer = java.util.Timer@455b6df1 ob_scala_eol
1.2.2.2 Running Scala Scripts
- Sample script
- file: src/scalaScript.scala
import javax.swing.{JFrame, JPanel, JTextArea} println("Hello world Scala") val frame = new JFrame("Sample scala script") frame.setSize(300, 400) frame.setDefaultCloseOperation(javax.swing.JFrame.EXIT_ON_CLOSE) val tarea = new JTextArea() val scroll = new javax.swing.JScrollPane(tarea) frame.add(scroll) frame.setVisible(true) tarea.append("Hello world Scala Script") tarea.append("\nHello world! (en)") tarea.append("\nHola mundo! (es)") tarea.append("\nOla mundo! (pt)")
- Running script from command line:
- Running the script command line with faster initialization.
scala -save scalaScript.scala
It will run the script and compile it to scalaScript.jar that will speed up the initialization when the scala program is invoked again.
$ scala -save scalaScript.scala Hello world Scala $ file scalaScript.jar scalaScript.jar: Java archive data (JAR) $ unzip -l scalaScript.jar Archive: scalaScript.jar Length Date Time Name --------- ---------- ----- ---- 75 2017-06-30 16:02 META-INF/MANIFEST.MF 1691 2017-06-30 16:02 Main$$anon$1.class 570 2017-06-30 16:02 Main$.class 556 2017-06-30 16:02 Main.class --------- ------- 2892 4 files # Next initialization is faster. $ scala scalaScript.scala Hello world Scala ob_scala_eol # The generated jar file can be executed directly. $ scala scalaScript.jar Hello world Scala ob_scala_eol # It be executed directly with java and Scala runtime library. $ java -cp /home/archbox/opt/scala-2.11.8/lib/scala-library.jar:scalaScript.jar Main Hello world Scala ob_scala_eol
- Running script from scala REPL.
$ scala Welcome to Scala 2.11.8 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_20). Type in expressions for evaluation. Or try :help. scala> :paste scalaScript.scala Pasting file scalaScript.scala... Hello world Scala scala> println(tarea.getText()) Hello world Scala Script Hello world! (en) Hola mundo! (es) Ola mundo! (pt) Write something more. Scala allows to write apps with GUIs fast!! Like in Smalltalk, in Scala all objects and functions are right at your fingers. scala> tarea.setText("Hello world Java Swing") scala>
- Scala script as Unix executable
Scala scripts can be executed as ordinary *nix shell scripts like bash scripts with ./scala-script.scala
Example:
file: src/scalaNix.scala - This script shows all arguments passed by the user and displays a file in GUI (Graphical User Interface) passed as first argument arg(0).
#!/bin/sh exec scala -save "$0" "$@" !# // Display text in a GUI def displayText(text: String) = { import javax.swing.{JFrame, JTextArea, JScrollPane} val tarea = new JTextArea() val frame = new JFrame() frame.add(new JScrollPane(tarea)) frame.setSize(400, 500) frame.setVisible(true) frame.setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE) tarea.setText(text) } def readFile(file: String) = { val src = scala.io.Source.fromFile(file) val txt = src.mkString src.close() txt } println("Testing Scala script") println("Arguments passed by user") args.foldLeft(0){(acc, a) => println(s"arg[${acc}] = ${a}") acc + 1 } displayText(readFile(args(0)))
Running it: It will display the file file:///etc/protocols in a Linux-based distribution passed as first argument arg(0).
$ src/scalaNix.scala /etc/protocols arg1 arg2 arg3 arg4 Testing Scala script Arguments passed by user arg[0] = /etc/protocols arg[1] = arg1 arg[2] = arg2 arg[3] = arg3 arg[4] = arg4
1.2.3 Scalac - Scala compiler
1.2.3.1 Sample scala program
- file: src/scalaProgram.scala
package scalaApp import javax.swing.{JFrame, JPanel, JTextArea} object Main{ def main(arrgs: Array[String]){ println("Hello world Scala") val frame = new JFrame("Sample scala script") frame.setSize(300, 400) frame.setDefaultCloseOperation(javax.swing.JFrame.EXIT_ON_CLOSE) val tarea = new JTextArea() val scroll = new javax.swing.JScrollPane(tarea) frame.add(scroll) frame.setVisible(true) tarea.append("Hello world Scala Script") tarea.append("\nHello world! (en)") tarea.append("\nHola mundo! (es)") tarea.append("\nOla mundo! (pt)") } }
1.2.3.2 Compiling
Compiling:
# Compile $ scalac scalaProgram.scala # Inspect generated files. $ ls scalaApp/ Main.class 'Main$.class' $ file scalaApp/Main.class scalaApp/Main.class: compiled Java class data, version 50.0 (Java 1.6)
Running with scala:
$ scala scalaApp.Main Hello world Scala
Running directly with java:
$ java -cp .:/home/archbox/opt/scala-2.11.8/lib/scala-library.jar scalaApp.Main Hello world Scala
Screenshot:
1.2.3.3 Compiling to a jar file
Compiling:
$ scalac scalaProgram.scala -d scalaApp.jar # Inspect generated file. $ file scalaApp.jar scalaApp.jar: Java archive data (JAR) # View package contents with jar tool. $ jar tf scalaApp.jar META-INF/MANIFEST.MF scalaApp/Main.class scalaApp/Main$.class # View package contents with unzip tool. $ unzip -l scalaApp.jar Archive: scalaApp.jar Length Date Time Name --------- ---------- ----- ---- 84 2017-06-30 16:50 META-INF/MANIFEST.MF 586 2017-06-30 16:50 scalaApp/Main.class 1369 2017-06-30 16:50 scalaApp/Main$.class --------- ------- 2039 3 files
Running with scala:
$ scala scalaApp.jar Hello world Scala
Running with java:
$ java -cp scalaApp.jar:/home/archbox/opt/scala-2.11.8/lib/scala-library.jar scalaApp.Main Hello world Scala
or
$ cp /home/archbox/opt/scala-2.11.8/lib/scala-library.jar . $ java -cp scalaApp.jar:scala-library.jar scalaApp.Main Hello world Scala
1.2.3.4 See also
- Setting an Application's Entry Point (The Java™ Tutorials > Deployment > Packaging Programs in JAR Files)
- Lesson: Packaging Programs in JAR Files (The Java™ Tutorials > Deployment)
- Creating a JAR File (The Java™ Tutorials > Deployment > Packaging Programs in JAR Files)
- Manipulating JARs, WARs, and EARs on the command line | JavaWorld
- How to make a JAR file Linux executable (Example)
- Packaging and Deploying Desktop Java Applications
- How to create desktop shortcut or launcher on Linux - Xmodulo
- Launch4j - Cross-platform Java executable wrapper
1.3 Basic Syntax
1.3.1 Comments
// Single line comment /* Multiline Comment */ /** * Documentation comment. * */
Cooment with Scala-Doc markup. It allows Scaladoc to extract the documentation from comment and produce an html documentation such as Doxygen for C or C++ and Javadoc for Java do.
/** This function computes the distance from origin (0, 0) to point (x, y) * * Usage Example: * * {{{ * scala> distancePoint(30.0, 40.0) * res11: Double = 50.0 * }}} * * @param x - X coordinate of point * @param y - Y coordinate of point * @return Distance from origin to point (x, y) */ def distancePoint(x: Double, y: Double) = Math.sqrt(x * x + y * y)
1.3.2 Values and Variables Declaration
Value
It is not possible to reassign values.
scala> val x = 10.2323 x: Double = 10.2323 scala> x = 1.5354 <console>:12: error: reassignment to val x = 1.5354 scala> val a = "hello world" a: String = hello world // Scala has multi-line string, unlike Java. val s = """Hello world Scala String """
It is not possible to change a reference to object stored in a value. For instance, the reference to the object of class/type JFrame with title "Hello World" cannot be changed, although the the object can be changed.
scala> val frame = new javax.swing.JFrame("Hello world") frame: javax.swing.JFrame = javax.swing.JFrame[frame0,0,27,0x0,invalid,hidden ...] // --- Try to change the reference stored in the value. // scala> val frame2 = new javax.swing.JFrame("Hello world") frame2: javax.swing.JFrame = javax.swing.JFrame[frame1,0,27 ...] scala> frame = frame2 <console>:13: error: reassignment to val frame = frame2 // A copy of the object previously defined is not created, // only the reference (pointer) of this object is copied. // scala> val frameCopy = frame frameCopy: javax.swing.JFrame = javax.swing.JFrame[frame3,0,2 ... .. scala> frame.getTitle res18: String = Hello world scala> frameCopy.getTitle res12: String = Hello world scala> frame.setTitle("Scalability and growth") scala> frameCopy.setTitle("Scalability and growth") scala> frame.getTitle res22: String = Scalability and growth scala> frame.setTitle("Scalable scalable scalable ...") scala> frameCopy.getTitle res24: String = Scalable scalable scalable ... // Eq - Method checks for reference equality. So, if the references (pointer to object) to // objects are equal, then the return value should be true. // scala> frameCopy eq frame res15: Boolean = true scala> frameCopy.eq(frame) res16: Boolean = true scala> frame.eq(frameCopy) res17: Boolean = true
Values declared with explicit type annotation:
scala> val x: Double = 20.03 x: Double = 20.03 scala> val y: Double = "hello world" <console>:11: error: type mismatch; found : String("hello world") required: Double val y: Double = "hello world" ^ scala> val point: (Int, Int) = (100, 200) point: (Int, Int) = (100,200) scala> val point2: (Int, Int) = -100.34 <console>:11: error: type mismatch; found : Double(-100.34) required: (Int, Int) val point2: (Int, Int) = -100.34 scala> type Action = () => Unit defined type alias Action scala> val act1: Action = () => println("Print to screen1") act1: Action = $$Lambda$1109/363103401@b56c222 scala> printer1("hello world Scala is super scalable!!") hello world Scala is super scalable!!
Variable
Unlike a value, a variable allows reassignment and change the reference to the pointed object.
scala> var x = 10.2334 x: Double = 10.2334 scala> x = 4.5 x: Double = 4.5 scala> var s = "Hello" s: String = Hello scala> s = "world" s: String = world scala>
Variables can be declared with type annotation too:
scala> var x: Double = 10.0 x: Double = 10.0 scala> x = 2 x: Double = 2.0 scala> x = 45.43 x: Double = 45.43 // Declare a variable holding a reference to a function // with default NULL value. // WARNING. Null should be used with caution. scala> var action: String => Unit = null action: String => Unit = null scala> action("testing Scala") java.lang.NullPointerException ... 28 elided scala> action = s => println("Message = " + s) action: String => Unit = $$Lambda$1171/1365490452@2865c8db scala> action("testing Scala") Message = testing Scala // Print to stderr - Store reference to method (.println) of System.err object. scala> action = System.err.println action: String => Unit = $$Lambda$1172/1358395794@2f533bd1 scala> action("testing Scala") testing Scala
1.3.3 Scope
val x = 10 val y = 15 val z = x + y scala> val x = 10 x: Int = 10 scala> val y = 15 y: Int = 15 scala> val z = x + y z: Int = 25 // Local Scope scala> { val x = 5 ; val y = 4 ; val z = x + y ; println("z = " + z) } z = 9 // Values from local scope are not affected by the values of local one. scala> x res4: Int = 10 scala> y res5: Int = 15 scala> val k1 = { println("k1 set to 5.0") ; 5.0 } k1 set to 5.0 k1: Double = 5.0 scala> 2 * k1 res1: Double = 10.0
Return value from scope:
val z2 = { val a = 2 val b = 5 // Return value from scope is the last value a + b } scala> val z2 = { | val a = 2 | val b = 5 | // Return value from scope is the last value | a + b | } z2: Int = 7 scala> a <console>:12: error: not found: value a a ^ scala> b <console>:12: error: not found: value b b ^
1.3.4 Import Java and Scala Libraries
Import everythign from namespace java.io. Equivalent to java statement (import java.io.*).
scala> import java.io._ import java.io._ scala> val fd = new FileWriter("/tmp/testfile.txt") fd: java.io.FileWriter = java.io.FileWriter@1eceaded scala> fd.write("hello world") scala> fd.close()
Import multiple classes
scala> import javax.swing.JFrame import javax.swing.JFrame scala> import javax.swing.{JFrame, JPanel, JLabel} import javax.swing.{JFrame, JPanel, JLabel} scala> val frame = new JFrame("Hello world Scala") scala> frame.setSize(300, 400) scala> frame.setVisible(true)
or
scala> val frame = new javax.swing.JFrame("Hello world Scala") scala> frame.setSize(300, 400) scala> frame.setVisible(true)
Import members / functions from a static class.
@ import Math.{PI, sin, cos, tan, exp} import Math.{PI, sin, cos, tan, exp} @ Math.sin(Math.PI) res7: Double = 1.2246467991473532E-16 @ sin(PI) res4: Double = 1.2246467991473532E-16 @ cos(PI) res5: Double = -1.0 @ exp(-2.0) res6: Double = 0.1353352832366127
or
scala> import Math._ // Import everything import Math._ // Or: Import all static methods of class java.lang.Math to use them as functions. scala> import java.lang.Math._ import java.lang.Math._ scala> sin _ res7: Double => Double = $$Lambda$1197/1482753855@56352274 scala> cos _ res8: Double => Double = $$Lambda$1198/1338109470@64deb236 scala> sin(PI) res9: Double = 1.2246467991473532E-16 scala> List(30, 45, 60, 90, 180).map{x => sin(x * PI / 180.0)} foreach println 0.49999999999999994 0.7071067811865475 0.8660254037844386 1.0 1.2246467991473532E-16
Import from singletons / objects
@ object Module{ def fn(x: Int) = x * 3 def fxy(a: Int, b: Int) = a + b val gravity = 9.81 } defined object Module @ Module.fn(4) res9: Int = 12 @ Module.fxy(10, 12) res10: Int = 22 @ import Module.{fn, fxy} import Module.{fn, fxy} @ fn(4) res12: Int = 12 @ fxy(10, 12) res13: Int = 22
Method Syntax
scala> Math.log10(1000) res16: Double = 3.0 scala> Math log10 1000 res17: Double = 3.0 scala> List(1.0, 10.0, 100.0, 1000.0, 10000.0).map(Math.log10) res20: List[Double] = List(0.0, 1.0, 2.0, 3.0, 4.0) scala> List(1.0, 10.0, 100.0, 1000.0, 10000.0) map Math.log10 res21: List[Double] = List(0.0, 1.0, 2.0, 3.0, 4.0)
1.4 Functions
1.4.1 Function Definition
def prod(x: Int, y: Int) = x * y scala> prod(4, 5) res11: Int = 20 def fun(a: Int, b: Int) = { val c = 3 * a + b val d = b - a c * d // The return value is the last value } /** a = 4 and b = 5 c = 3 * a + b = 3 * 4 + 5 = 17 d = b - a = 5 - 4 = 1 Return value: c * d = 17 * 1 --------------- */ scala> fun(4, 5) res8: Int = 17 def showFiles(path: String) = { val file = new java.io.File(path) file.listFiles.foreach(println) } // Pasting in the REPL scala> def showFiles(path: String) = { | val file = new java.io.File(path) | file.listFiles.foreach(println) | } showFiles: (path: String)Unit scala> showFiles("/") /home /var /bin /usr /root /Applications /proc /boot /dev ... ...
1.4.2 Anonymous Functions / Lambda Functions or Function Literals
Simple Anonymous Functions
scala> val mulBy10 = (x: Int) => x * 10 mulBy10: Int => Int = <function1> scala> mulBy10(5) res25: Int = 50 scala> scala> val add = (x: Double, y: Double) => x + y addV1: (Double, Double) => Double = <function2> scala> add(10, 20) res26: Double = 30.0
Multi line anonymous functions
val func = (a: Double, b: Double) => { val m = a * b val n = a * a * 3 - 4.5 * b (m, n, m + n) } scala> val func = (a: Double, b: Double) => { | val m = a * b | val n = a * a * 3 - 4.5 * b | (m, n, m + n) | } func: (Double, Double) => (Double, Double, Double) = <function2> scala> func(3, 5) res28: (Double, Double, Double) = (15.0,4.5,19.5) scala> func(4, 3) res29: (Double, Double, Double) = (12.0,34.5,46.5) scala>
1.4.3 Curried Functions
Function in non-curried form (Tuple):
scala> def mulxy (x: Int, y: Int) = x * y mulxy: (x: Int, y: Int)Int scala> mulxy(3, 4) res37: Int = 12 scala> List(1, 2, 3, 4, 5).map(mulxy(3, _)) res38: List[Int] = List(3, 6, 9, 12, 15) scala> List(1, 2, 3, 4, 5).map(mulxy(_, 4)) res39: List[Int] = List(4, 8, 12, 16, 20)
Function in Curried Form:
scala> def mulxy (x: Int) (y: Int) = x * y mulxy: (x: Int)(y: Int)Int scala> mulxy _ res89: Int => (Int => Int) = <function1> scala> mulxy(3)_ res88: Int => Int = <function1> scala> mulxy(3)(4) res90: Int = 12 scala> List(2, 3, 4, 5).map(mulxy(5)) res91: List[Int] = List(10, 15, 20, 25) scala> List(2, 3, 4, 5) map mulxy(5) res38: List[Int] = List(10, 15, 20, 25)
Curried anonymous functions
scala> val mulNonCurried = (x: Int, y: Int) => x * y mulNonCurried: (Int, Int) => Int = <function2> scala> mulNonCurried(3, 5) res30: Int = 15 scala> val mulCurried = (x: Int) => (y: Int) => x * y mulCurried: Int => (Int => Int) = <function1> scala> mulCurried(5) res32: Int => Int = <function1> scala> mulCurried(5)(4) res33: Int = 20 scala> List(1, 2, 3, 4, 5).map(mulCurried(4)) res34: List[Int] = List(4, 8, 12, 16, 20) scala> List(1, 2, 3, 4, 5) map mulCurried(4) res35: List[Int] = List(4, 8, 12, 16, 20)
1.4.4 Closures
1.4.4.1 Simple closure example
def makeMultiplier(factor: Double) = { val m = (factor + 1.0) * factor val n = factor / 100.0 (x: Double) => x * m + n } scala> def makeMultiplier(factor: Double) = { | val m = (factor + 1.0) * factor | val n = factor / 100.0 | (x: Double) => x * m + n | } makeMultiplier: (factor: Double)Double => Double scala> val fn1 = makeMultiplier(3.0) fn1: Double => Double = <function1> scala> val fn2 = makeMultiplier(4.0) fn2: Double => Double = <function1> scala> fn1(1) res40: Double = 12.03 scala> fn1(2) res41: Double = 24.03 scala> fn2(1) res42: Double = 20.04 scala> fn1(2) res43: Double = 24.03
1.4.4.2 Stateful functions
Example 1
// Version 1 // def makeIncrementer1() = { var counter = 0 val inc = () => { val c = counter counter = counter + 1 c } inc } scala> def makeIncrementer1() = { | var counter = 0 | val inc = () => { | val c = counter | counter = counter + 1 | c | } | | inc | } makeIncrementer1: ()() => Int scala> val inc = makeIncrementer1() inc: () => Int = <function0> scala> inc() res22: Int = 0 scala> inc() res23: Int = 1 scala> inc() res24: Int = 2 scala> inc() res25: Int = 3 scala> inc() res26: Int = 4 scala> val inc2 = makeIncrementer1() inc2: () => Int = <function0> scala> inc2() res27: Int = 0 scala> inc2() res28: Int = 1 scala> inc2() res29: Int = 2 scala> inc2() res30: Int = 3 ... ... // Version 2 // def makeIncrementer2() = { var counter = 0 () => { val c = counter counter = counter + 1 c } } scala> def makeIncrementer2() = { | var counter = 0 | () => { | val c = counter | counter = counter + 1 | c | } | } makeIncrementer2: ()() => Int scala> val inc3 = makeIncrementer makeIncrementer1 makeIncrementer2 scala> val inc3 = makeIncrementer2() inc3: () => Int = <function0> scala> inc3() res31: Int = 0 scala> inc3() res32: Int = 1 scala> inc3() res33: Int = 2 scala> val inc4 = makeIncrementer2() inc4: () => Int = <function0> scala> inc4() res34: Int = 0 scala> inc4() res35: Int = 1 scala> inc4() res36: Int = 2 ...
Example 2
def makeCounter() = { var counter = 0 val inc = () => { val c = counter counter = counter + 1 c } val dec = () => { val c = counter counter = counter - 1 c } (inc, dec) } scala> def makeCounter() = { | var counter = 0 | | val inc = () => { | val c = counter | counter = counter + 1 | c | } | | val dec = () => { | val c = counter | counter = counter - 1 | c | } | | (inc, dec) | } makeCounter: ()(() => Int, () => Int) scala> val (inc, dec) = makeCounter() inc: () => Int = <function0> dec: () => Int = <function0> scala> inc() res48: Int = 0 scala> inc() res49: Int = 1 scala> inc() res50: Int = 2 scala> dec() res51: Int = 3 scala> dec() res52: Int = 2 scala> dec() res53: Int = 1 scala> dec() res54: Int = 0 scala> inc() res55: Int = -1 scala> inc() res56: Int = 0 scala> inc() res57: Int = 1
1.4.4.3 Emulating Objects with closures
// Record of functions // case class Counter( increment: () => Unit ,decrement: () => Unit ,get: () => Int ) // The internal state counter can only be accessed using the "methods" or // functions increment, decrement and get. // def newCounter(init: Int) = { var counter = init Counter( () => { counter = counter + 1} ,() => { counter = counter - 1} ,() => counter ) } scala> val c = newCounter(0) c: Counter = Counter(<function0>,<function0>,<function0>) scala> c.increment _ res1: () => () => Unit = <function0> scala> c.decrement _ res2: () => () => Unit = <function0> scala> c.get get getClass scala> c.get _ res3: () => () => Int = <function0> scala> c.get() res11: Int = 0 scala> c.increment() scala> c.get() res13: Int = 1 scala> c.increment() scala> c.get() res15: Int = 2 scala> c.increment() ; c.get() res16: Int = 3 scala> c.increment() ; c.get() res17: Int = 4 scala> c.increment() ; c.get() res18: Int = 5 scala> c.decrement() ; c.get() res19: Int = 4 scala> c.decrement() ; c.get() res20: Int = 3 scala> c.decrement() ; c.get() res21: Int = 2
1.4.5 Nested Functions
As Scala has closures or lexical scope, it is possible to define functions inside functions which avoids code repetition and polluting the local scope.
/** Pretty print a collection of tuples as a table. Parameters: @rows - Collection of tuples to be printed. @title - Tuple containing the titles of left and right side. @line - Flag that if set to true, prints a new line between each row. @margin - Margin from left side of screen as number of spaces. @sep - Number of spaces between left side and right side @maxRside - Maximum number of characters to printed on right side. */ def printTupleAsTable( rows: Seq[(String, String)], title: (String, String) = ("", ""), line: Boolean = false, margin: Int = 0, sep: Int = 4, maxRside: Int = 100 ) = { def printRow(wmax1: Int, clamp: Boolean = true) = (row: (String, String)) => { val (lside, rside) = row // print left margin for (a <- 0 to margin) print(' ') print(lside) // Print spaces for (a <- 0 to wmax1 - lside.length + sep) print(' ') if (rside.length <= maxRside) { println(rside) } else if (clamp){ val dots = "..." println(rside.take(maxRside - dots.length) + dots) } else { println(rside.take(maxRside)) } // Print line between rows if (line) println() } def printDashes(wmax1: Int) = { printRow(wmax1, false)("-" * wmax1, "-" * maxRside) } val (title1, title2) = title // Maximum length of left side column val wmax1 = title1.length max rows.map(row => row._1.length).max printRow(wmax1)(title) printDashes(wmax1) rows foreach printRow(wmax1) printDashes(wmax1) }
Running:
import scala.collection.JavaConverters._ scala> System.getProperties.asScala.toSeq foreach println (env.emacs,) (java.runtime.name,OpenJDK Runtime Environment) (sun.boot.library.path,/usr/lib/jvm/java-8-openjdk/jre/lib/amd64) (java.vm.version,25.141-b15) (java.vm.vendor,Oracle Corporation) (java.vendor.url,http://java.oracle.com/) (path.separator,:) (java.vm.name,OpenJDK 64-Bit Server VM) (file.encoding.pkg,sun.io) (user.country,US) (sun.java.launcher,SUN_STANDARD) ... ... ... ... ... ... ... ... ... ... ... ... ... ... scala> printTupleAsTable(System.getProperties.asScala.toSeq, title = ("Java Property", "Value"), maxRside = 60) Java Property Value ----------------------------- ------------------------------------------------------------ env.emacs java.runtime.name OpenJDK Runtime Environment sun.boot.library.path /usr/lib/jvm/java-8-openjdk/jre/lib/amd64 java.vm.version 25.141-b15 java.vm.vendor Oracle Corporation java.vendor.url http://java.oracle.com/ path.separator : java.vm.name OpenJDK 64-Bit Server VM file.encoding.pkg sun.io user.country US sun.java.launcher SUN_STANDARD sun.os.patch.level unknown java.vm.specification.name Java Virtual Machine Specification user.dir /home/archbox/test ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... . java.vm.info mixed mode java.version 1.8.0_141 java.ext.dirs /usr/lib/jvm/java-8-openjdk/jre/lib/ext:/usr/java/package... sun.boot.class.path /usr/lib/jvm/java-8-openjdk/jre/lib/resources.jar:/usr/li... java.vendor Oracle Corporation file.separator / java.vendor.url.bug http://bugreport.sun.com/bugreport/ sun.io.unicode.encoding UnicodeLittle sun.cpu.endian little sun.cpu.isalist ----------------------------- ------------------------------------------------------------ printTupleAsTable( rows = System.getenv.asScala.toSeq, title = ("Environment Variables", "Value"), margin = 5, maxRside = 60 ) scala> printTupleAsTable( | rows = System.getenv.asScala.toSeq, | title = ("Environment Variables", "Value"), | margin = 5, | maxRside = 60 | ) Environment Variables Value ------------------------ ------------------------------------------------------------ PATH /usr/local/sbin:/usr/local/bin:/usr/bin:/usr/lib/jvm/defa... XAUTHORITY /home/archbox/.Xauthority LC_MEASUREMENT pt_BR.UTF-8 LC_TELEPHONE pt_BR.UTF-8 GDMSESSION xfce XDG_DATA_DIRS /usr/local/share:/usr/share LC_TIME pt_BR.UTF-8 DBUS_SESSION_BUS_ADDRESS unix:path=/run/user/1001/bus XDG_CURRENT_DESKTOP XFCE MAIL /var/spool/mail/archbox SSH_AGENT_PID 29199 MOZ_PLUGIN_PATH /usr/lib/mozilla/plugins COLORTERM truecolor SESSION_MANAGER local/ghostpc:@/tmp/.ICE-unix/29194,unix/ghostpc:/tmp/.IC... LC_PAPER pt_BR.UTF-8 LOGNAME archbox PWD /home/archbox/test ... .... ... ... .... ... ... .... ... ... .... ... ... .... ... ... .... ... EDITOR emacs -Q -nw --no-site -eval "(progn (setq inhibit-start... NLSPATH /usr/dt/lib/nls/msg/%L/%N.cat QT_QPA_PLATFORMTHEME qt5ct XDG_RUNTIME_DIR /run/user/1001 XDG_VTNR 7 HOME /home/archbox ------------------------ ------------------------------------------------------------
1.4.6 Function Composition
Math Composition
Computes f.compose(g) = f°g (x) = f(g(x))
- f°g (3) = f(g(3)) = f(2*3) = f(6) = 6 + 10 = 16 ok.
f ° g = f(g(x)) .................................................... . ___________________ ___________________ . . | | | | . . | | | | . --+->+ g(x) = x * 2 +-->---+ f(x) = x + 10 +----+--> 4 . | g(4) = 8 | 8 | f(8) = 18 | . 18 . |_________________| +-----------------+ . . . .................................................... ................ . . 4 -->+ (f ° g) (x) +--> 18 . f(g(x)) . ................
scala> val f = (x: Int) => x + 10 f: Int => Int = <function1> scala> val g = (x: Int) => x * 2 g: Int => Int = <function1> scala> val comp1 = f.compose(g) comp1: Int => Int = <function1> scala> comp1(3) res70: Int = 16 scala> List(1, 2, 3, 4, 5).map(comp1) res71: List[Int] = List(12, 14, 16, 18, 20) scala> /// It could also be: scala> val comp11 = f compose g comp11: Int => Int = <function1> scala> List(1, 2, 3, 4, 5).map(comp11) res72: List[Int] = List(12, 14, 16, 18, 20)
Reverse Composition (andThen)
- f.andThen(g) = f >> g = g(f(x))
- (f andThen g)(4) = (f >> g)(4) = g(f(4)) = g(14) = 28 . Ok.
f >> g = g ° f = g(f(x)) .................................................... . ___________________ ___________________ . . | | | | . . | | | | . ---->+ f(x) = x + 10 +-->---+ g(x) = x * 2 +----+--> 4 . | f(4) = 14 | 14 | g(14) = 28 | . 28 . |_________________| +-----------------+ . . . .................................................... ................. . . 4 -->+ (f >> g) (x) +--> 28 . g(f(x)) . .................
scala> val f = (x: Int) => x + 10 f: Int => Int = <function1> scala> val g = (x: Int) => x * 2 g: Int => Int = <function1> scala> val f_rcomp_g = f andThen g f_rcomp_g: Int => Int = <function1> scala> f_rcomp_g (4) res76: Int = 28 // Or scala> f.andThen(g)(4) res77: Int = 28
1.4.7 Higher Order Functions
def sumFn1(f: Int => Int, g: Int => Int, x: Int) = f(x) + g(x) scala> def sumFn1(f: Int => Int, g: Int => Int) (x: Int) = f(x) + g(x) sumFn: (f: Int => Int, g: Int => Int)(x: Int)Int scala> sumFn1(x => x * 4, a => a + 5, 4) res46: Int = 25 scala> sumFn1(x => x * 4, a => a + 5, 5) res47: Int = 30 scala> sumFn1(x => x * x, a => a + 5, 5) res48: Int = 35 def sumFn2(f: Int => Int, g: Int => Int) = (x: Int) => f(x) + g(x) scala> f1(3) res49: Int = 20 scala> f1(5) res50: Int = 30 scala> val f2 = sumFn2(x => x * x, a => a + a) f2: Int => Int = <function1> scala> f2(3) res51: Int = 15 scala> f2(5) res52: Int = 35 def iterFiles(fn: String => Unit) = (path: String) => { val f = new java.io.File(path) f.listFiles().foreach(file => fn(file.toString)) } scala> iterFiles(println)("/") /home /var /bin /usr /root /Applications /proc /boot /dev ... scala> val showFiles = iterFiles(println) showFiles: String => Unit = <function1> scala> showFiles("/etc") /etc/systemd /etc/motd /etc/gemrc /etc/adobe /etc/ld.so.cache /etc/environment /etc/libreoffice /etc/rc_keymaps /etc/sensors3.conf ... ...
1.4.8 Polymorphic Functions
1.4.8.1 Generic Functions - functions with type parameters
def identity[A](x: A) = x scala> def identity[A](x: A) = x identity: [A](x: A)A scala> identity(100) res4: Int = 100 scala> identity(Some(300)) res5: Some[Int] = Some(300) scala> identity("Hello world") res6: String = Hello world def constantly[A, B](a: A) = (b: B) => a scala> constantly(100) res7: Any => Int = <function1> scala> constantly(100)("Hello") res8: Int = 100 scala> constantly(100)("world") res9: Int = 100 scala> constantly(100)(Some(400)) res10: Int = 100 scala> def show[A](a: A) = a.toString show: [A](a: A)String scala> show(340.343) res12: String = 340.343 scala> show(Some(1000)) res13: String = Some(1000) scala> show(None) res14: String = None
1.4.8.2 Functions with multiple signatures
Multiple functions can share the same name with different signatures if they don't have any default parameter.
object Dialog { def showAlert(message: String){ javax.swing.JOptionPane.showMessageDialog( null ,message ,"Alert" ,javax.swing.JOptionPane.WARNING_MESSAGE ) } def showAlert(title: String, message: String){ javax.swing.JOptionPane.showMessageDialog( null ,message ,title ,javax.swing.JOptionPane.WARNING_MESSAGE ) } }
Example:
scala> Dialog.showAlert("Error: Network failure. Could not fetch data") scala> Dialog.showAlert("Error report", "Error: Network failure. Could not fetch data")
1.4.9 Storing Functions in Data Structures
As in any language with first class functions, functions can be stored in data structures such as lists, arrays, maps and also stored in objects.
- Storing functions in variables:
scala> var printer: (String) => Unit = s => () printer: String => Unit = $$Lambda$1031/1414431049@7fb66650 scala> printer("Test printer") scala> printer = println printer: String => Unit = $$Lambda$1060/1256918571@648c5fb3 scala> printer("Test printer") Test printer // Print to stdout scala> printer = System.err.println printer: String => Unit = $$Lambda$1092/1353172779@54c11750 scala> printer("Test printer") Test printer
- References to methods can also be stored in variables of function type. In this example, the variable action stores a reference to the method _.showInventory of an instance of the class ProductInventory.
scala> var action: () => Unit = () => () action: () => Unit scala> action() scala> val printMe = () => println("Print me right now") printMe: () => Unit scala> action = printMe action: () => Unit scala> action() Print me right now object Utils{ def showLinuxRootFiles() = for(f <- new java.io.File("/").listFiles) println(f) } scala> action = Utils.showLinuxRootFiles _ action: () => Unit scala> action() /etc /tmp /sbin /sys /opt /media /boot ... ... ... ... ... ... ... ... class ProductInventory{ private val lst = new java.util.ArrayList[String] def addProduct(product: String) = { println("Logger -> Added " + product) lst.add(product) } def showInventory() = lst.forEach{ p => println("Product = " + p)} } scala> val myShopInventory = new ProductInventory() myShopInventory: ProductInventory = ProductInventory@5a6dbab7 scala> myShopInventory.addProduct("Orange") Logger -> Added Orange res11: Boolean = true scala> myShopInventory.addProduct("Apple") Logger -> Added Apple res12: Boolean = true scala> myShopInventory.addProduct("Coffee beans") Logger -> Added Coffee beans res13: Boolean = true scala> myShopInventory.showInventory() Product = Orange Product = Apple Product = Coffee beans scala> action = myShopInventory.showInventory _ action: () => Unit = $$Lambda$1369/801054059@4f4a6252 scala> action() Product = Orange Product = Apple Product = Coffee beans scala> myShopInventory.addProduct("Strawberry") Logger -> Added Strawberry res18: Boolean = true scala> action() Product = Orange Product = Apple Product = Coffee beans Product = Strawberry
- Store functions in arrays, hash maps (dictionaries) and lists:
// Dummy - do anything scala> val action0 = () => () action0: () => Unit = $$Lambda$1375/1357073725@74ddf6f8 scala> val action1 = () => println("Get product from inventory") action1: () => Unit = $$Lambda$1373/1589324702@6bfa9cf5 scala> val action2 = () => println("Order more replacement parts from the manufacturer X inc.") action2: () => Unit = $$Lambda$1374/180387583@1c9eb283 def action3() = println("Check company's balance sheet.") class CompanyData(name: String){ def printStatements() = { println("Print Balance Sheet of company = " + name) println("Print Cash Flow statement of company = " + name) } } scala> val company = new CompanyData("XZYZW Solution. Inc.") company: CompanyData = CompanyData@249318e7 //------ Register functions --------------- // scala> import scala.collection.mutable.ListBuffer import scala.collection.mutable.ListBuffer scala> val actionList = ListBuffer[() => Unit]() actionList: scala.collection.mutable.ListBuffer[() => Unit] = ListBuffer() scala> actionList.append(action0) scala> actionList.append(action1) scala> actionList.append(action2) scala> for (act <- actionList) act() Get product from inventory Order more replacement parts from the fanufacterer X inc. actionList.append(action3 _) scala> for (act <- actionList) act() Get product from inventory Order more replacement parts from the fanufacterer X inc. Check company's balance sheet. // Register method to be called scala> actionList.append(company.printStatements _) scala> for (act <- actionList) act() Get product from inventory Order more replacement parts from the fanufacterer X inc. Check company's balance sheet. Print Balance Sheet of company = XZYZW Solution. Inc. Print Cash Flow statement of company = XZYZW Solution. Inc.
1.4.10 Variadic - function or functions with variable number of arguments:
Basic example:
def varfun(inputs: String*) = { println("I got the parameters: ") inputs.foreach(println) } scala> varfun("Hello", "World", "Scala", "Rocks") I got the parameters: Hello World Scala Rocks
Pass collections as arguments of variadic functions:
scala> val xs = List("hello", "World", "Scala", "Rocks") xs: List[String] = List(hello, World, Scala, Rocks) scala> varfun(xs) <console>:14: error: type mismatch; found : List[String] required: String varfun(xs) ^ scala> varfun(xs : _ *) I got the parameters: hello World Scala Rocks scala> val xs2 = Vector("Java", "everywhere", "in", "desktop", "servers", "mobile") xs2: scala.collection.immutable.Vector[String] = Vector(Java, everywhere, in, desktop, servers, mobile) scala> varfun(xs2 :_ *) I got the parameters: Java everywhere in desktop servers mobile
1.4.11 Functions with default parameters
/** Free fall speed v(t) = g * t, g in m/s^2 */ def freeFallSpeed(time: Double, gravity: Double = 9.81) = time * gravity scala> freeFallSpeed(1.0) res30: Double = 9.81 scala> freeFallSpeed(2.0) res31: Double = 19.62 scala> freeFallSpeed(2.0, 10.0) res32: Double = 20.0 scala> freeFallSpeed(2.0, 20.0) res33: Double = 40.0 scala> freeFallSpeed(2.0, gravity = 10.0) res35: Double = 20.0 scala> freeFallSpeed(2.0, gravity = 15.0) res36: Double = 30.0
1.4.12 Functions with call-by-name parameters
1.4.12.1 call-by-value or pass-by-value
In Scala function parameters are passed by value by default. They are evaluated before function application.
scala> val rnd = new java.util.Random() rnd: java.util.Random = java.util.Random@3198594d def passByValue(x: Int) = List(x, x, x) scala> passByValue(rnd.nextInt(10)) res31: List[Int] = List(2, 2, 2) scala> passByValue(rnd.nextInt(10)) res32: List[Int] = List(1, 1, 1) scala> rnd.nextInt(10) res12: Int = 2 scala> rnd.nextInt(10) res13: Int = 3 scala> rnd.nextInt(10) res14: Int = 4
1.4.12.2 call-by-name or pass-by-name
In the call-by-name evaluation strategy the function parameter passed by name is not evaluated before the function application such as in the pass-by-value strategy and they are evaluated every time they are referenced.
Example 1: The parameter x is passed-by-name and it is evaluated every
time it appears in the function body as the expression rnd.nextInt(10)
.
def passByName(x: => Int) = List(x, x, x) scala> passByName(rnd.nextInt(10)) res33: List[Int] = List(7, 8, 7) scala> passByName(rnd.nextInt(10)) res34: List[Int] = List(6, 4, 0) scala> passByName{rnd.nextInt(10)} res35: List[Int] = List(7, 4, 8) scala> passByName{rnd.nextInt(10)} res36: List[Int] = List(5, 9, 8)
It is equivalent to:
def passByNameSimulation(x: () => Int) = List(x(), x(), x()) scala> passByNameSimulation(() => rnd.nextInt(10)) res37: List[Int] = List(7, 4, 0) scala> passByNameSimulation(() => rnd.nextInt(10)) res38: List[Int] = List(2, 6, 1) scala> passByNameSimulation(() => rnd.nextInt(10)) res39: List[Int] = List(1, 0, 7)
Example 2:
def do3Times(fn: => Unit){ fn ; fn ; fn } scala> do3Times{ println("Hello world") } Hello world Hello world Hello world
1.4.12.3 Custom control structures with call-by-name
Pass-by-name parameters are useful for implementing functions that works like custom control structures and to pass code blocks as function parameters.
Example 1: Some custom "control structures".
def dotimes(n: Int)(fn: => Unit){ for (i <- 1 to n) fn } dotimes(3){ println("Scala is amazing.") println("Scala's super powers.\n") } scala> dotimes(3){ | println("Scala is amazing.") | println("Scala's super powers.\n") | } Scala is amazing. Scala's super powers. Scala is amazing. Scala's super powers. Scala is amazing. Scala's super powers. def doWhile(cond: => Boolean)(fn: => Unit){ fn if (cond) doWhile{cond}{fn} } var x = 5 doWhile(x > 0){ println("x = " + x) x = x - 1 } scala> var x = 5 x: Int = 5 scala> doWhile(x > 0){ | println("x = " + x) | x = x - 1 | } x = 5 x = 4 x = 3 x = 2 x = 1 // delay in mili seconds. def doAfterDelay(delay: Int)(action: => Unit) = { java.lang.Thread.sleep(delay) action } scala> doAfterDelay(2000){ println("2 seconds delay") } 2 seconds delay def loopDelay(delay: Int)(action: => Unit) { java.lang.Thread.sleep(delay) action loopDelay(delay){action} } scala> loopDelay(1000){ println("Print it forever every 1 second") } Print it forever every 1 second Print it forever every 1 second Print it forever every 1 second Print it forever every 1 second ... ... ...
Example 2: Passing code blocks as java swing event handlers.
/** Function that when executed removes the event handler */ type Dispose = () => Unit /** Subscribes to button click event */ def onButtonClick(button: javax.swing.JButton) (handler: => Unit) : Dispose = { val listener = new java.awt.event.ActionListener(){ def actionPerformed(evt: java.awt.event.ActionEvent) = { handler } } button.addActionListener(listener) // Returns function that when executed disposes the event handler () => button.removeActionListener(listener) } var n = 0 val frame = new javax.swing.JFrame("Click on the button") val panel = new javax.swing.JPanel() val button = new javax.swing.JButton("Click me right now!") val label = new javax.swing.JLabel(s"I was clicked ${n} times.") panel.add(button) panel.add(label) frame.add(panel) frame.setSize(300, 400) frame.setVisible(true) val dispose1 = onButtonClick(button){ println(s"The user clicked the button ${n} times.") label.setText(s"I was clicked ${n} times.") n = n + 1 } scala> val dispose1 = onButtonClick(button){ | println(s"The user clicked the button ${n} times.") | label.setText(s"I was clicked ${n} times.") | n = n + 1 | } dispose1: Dispose = <function0> // Run dispose1 to remove event handler. scala> dispose1()
1.4.13 Singleton or Module
A singleton object is an object of a class with a single instance implementing the singleton design pattern. A singletion object can be used as an ML-module which is a container for grouping functions.
cala> :paste // Entering paste mode (ctrl-D to finish) object Module { val code = "xyzfmnk" var amount = 1000 def sayHello () = println("Hello world") def fn(x: Int, y: Int) = 3 * x + 4 * y def showFiles(path: String) = { val files = (new java.io.File(path)).listFiles().filter(_.isFile) files.foreach(println) } def showDirectories(path: String) = { val files = (new java.io.File(path)).listFiles().filter(_.isDirectory) files.foreach(println) } } scala> Module.fn _ res204: (Int, Int) => Int = <function2> scala> Module.sayHello _ res205: () => Unit = <function0> scala> Module.showFiles _ res206: String => Unit = <function1> scala> Module.showFiles("/boot") /boot/initramfs-3.10-x86_64.img /boot/vmlinuz-4.9-x86_64 /boot/initramfs-3.10-x86_64-fallback.img /boot/vmlinuz-3.10-x86_64 /boot/initramfs-4.9-x86_64-fallback.img /boot/initramfs-4.9-x86_64.img /boot/intel-ucode.img /boot/linux310-x86_64.kver /boot/linux49-x86_64.kver scala> Module.showDirectories("/boot/grub") /boot/grub/i386-pc /boot/grub/locale /boot/grub/fonts /boot/grub/themes scala> Module.sayHello() Hello world scala> Module.code res219: String = xyzfmnk scala> Module.code = "hello" <console>:13: error: reassignment to val Module.code = "hello" ^ scala> Module.amount += 300 scala> Module.amount res223: Int = 1300 scala> Module.amount = 0 Module.amount: Int = 0 scala> Module.getClass() res224: Class[_ <: Module.type] = class Module$
Singletons can implement interfaces or traits.
trait Observer{ def onChange(): Unit def onError(): Unit } class Observer1 extends Observer{ def onChange() = println("I received a change event.") def onError() = println("I received an error event.") } object SingleTonObserver extends Observer{ def onChange() = println("Singleton: I received a change event.") def onError() = println("Singleton: I received an error event.") } scala> obs1.onChange() I received a change event. scala> obs1.onError() I received an error event. scala> SingleTonObserver.onChange() Singleton: I received a change event. scala> SingleTonObserver.onError() Singleton: I received an error event. def generateErrorEvent(obs: Observer) = obs.onError() scala> generateErrorEvent(obs1) I received an error event. scala> generateErrorEvent(SingleTonObserver) Singleton: I received an error event.
1.5 Imperative Constructs
1.5.1 While loop
var i = 0 while (i < 10){ println ("i = " + i) i = i + 1 } scala> var i = 0 i: Int = 0 scala> while (i < 10){ | println ("i = " + i) | i = i + 1 | } i = 0 i = 1 i = 2 i = 3 i = 4 i = 5 i = 6 i = 7 i = 8 i = 9
1.5.2 Do-while loop
val fr = new java.io.FileReader("/etc/protocols") val bfr = new java.io.BufferedReader(fr) val maxLines = 15 var line = "" var n = 0 do{ line = bfr.readLine() println(s"Line $n = " + line) n = n + 1 } while(line != null && n < maxLines) println(s"Number of lines read is equal to $n ") fr.close() bfr.close()
Output:
scala> do{ | line = bfr.readLine() | println(s"Line $n = " + line) | n = n + 1 | } while(line != null && n < maxLines) Line 0 = # /etc/protocols: Line 1 = # $Id: protocols,v 1.12 2016/07/08 12:27 ovasik Exp $ Line 2 = # Line 3 = # Internet (IP) protocols Line 4 = # Line 5 = # from: @(#)protocols 5.1 (Berkeley) 4/17/89 Line 6 = # Line 7 = # Updated for NetBSD based on RFC 1340, Assigned Numbers (July 1992). Line 8 = # Last IANA update included dated 2011-05-03 Line 9 = # Line 10 = # See also http://www.iana.org/assignments/protocol-numbers Line 11 = Line 12 = ip 0 IP # internet protocol, pseudo protocol number Line 13 = hopopt 0 HOPOPT # hop-by-hop options for ipv6 Line 14 = icmp 1 ICMP # internet control message protocol scala> println(s"Number of lines read is equal to $n ") Number of lines read is equal to 15
1.5.3 For-loop
Note: Scala's for-loop is not equivalent to the Java's for-loop, it is actually a syntax sugar for the method .foreach, so the Scala's for-loop has a performance overhead which may be significant for numerical or array operations. In this case the primitive while loop is faster and equivalent to the Java's for-loop.
scala> for (i <- 1 to 10) println(i) 1 2 3 4 5 6 7 8 9 10 scala> for (i <- 1 to 10) println("i = " + i) i = 1 i = 2 i = 3 i = 4 i = 5 i = 6 i = 7 i = 8 i = 9 i = 10 scala> for (file <- (new java.io.File("/").listFiles)) println(file) /home /var /bin /usr /root /Applications /proc /boot /dev /opt /etc /mnt /tmp /run /desktopfs-pkgs.txt /lib /.manjaro-tools /srv /lib64 /rootfs-pkgs.txt /sys /sbin /lost+found
1.6 String Manipulation
1.6.1 Basic string operations
@ val s = " Hello world scala " s: String = " Hello world scala "
Get string length
@ s.size res41: Int = 21 @ @ s size res42: Int = 21 @ @ s.length res43: Int = 21 @ s length res44: Int = 21 @
Concatenate string
@ "Hello" + " " + "world" + " " + "scala" res45: String = "Hello world scala" @
Trim
@ s res47: String = " Hello world scala " @ s trim res48: String = "Hello world scala" @ s.trim() res49: String = "Hello world scala" @ s trim res50: String = "Hello world scala" @
Replace string
@ s.replace("scala", "haskell") res46: String = " Hello world haskell " @
Upper and lower case
@ s.toUpperCase res60: String = " HELLO WORLD SCALA " @ s.toLowerCase res61: String = " hello world scala " @
Check if string starts with prefix
@ "hello world".startsWith("hello") res65: Boolean = true @ @ "hello world" startsWith "hello" res66: Boolean = true @
Check if string ends with suffix
@ "hello world".endsWith("hello") res67: Boolean = false @ @ "hello world".endsWith("world") res68: Boolean = true @ @ "hello world" endsWith "world" res69: Boolean = true @
String equality test
Equality checkign (==) is not type-safe due to interoperability with
Java. Some libraries such as Scalaz provides a type-safe equality
check operator (=
).
// Operator (==) is not type-safe: // @ "hello" == "world" res51: Boolean = false @ "hello" == "hello" res52: Boolean = true // This inconsistent operation type checks. @ "hello" == 100 res53: Boolean = false @
1.6.2 String Interpolation
1.6.2.1 Example 1
scala> val x = 10.24 x: Double = 10.24 scala> val y = 2.5413 y: Double = 2.5413 scala> val text = s"The value of x is $x and the value of y is $y, the sum of x + y = ${x + y}" text: String = The value of x is 10.24 and the value of y is 2.5413, the sum of x + y = 12.7813 @ "the value of square root of %.3f is equal to %.3f".format(10.0, Math.sqrt(10.0)) res70: String = "the value of square root of 10.000 is equal to 3.162" @ @ printf("the value of square root of %.3f is equal to %.3f\n", 10.0, Math.sqrt(10.0)) the value of square root of 10.000 is equal to 3.162 @ "Sum of array = %d".format(sum(Array(1, 2, 3, 4))) res78: String = "Sum of array = 10" @
1.6.2.2 Example 2
@ val xs = List(1, 2, 3, 4, 5) xs: List[Int] = List(1, 2, 3, 4, 5) @ @ val str = "hello world" str: String = "hello world" @ @ s"xs = ${xs} ; sum(xs) = ${xs sum} ; str = '${str}' ; size(str) = ${str size} " res86: String = "xs = List(1, 2, 3, 4, 5) ; sum(xs) = 15 ; str = 'hello world' ; size(str) = 11 " @
1.6.3 Join multiple strings by common separators
scala> List("Oversee", "fixed", "income", "assets").mkString(", ") res11: String = Oversee, fixed, income, assets scala> List("Oversee", "fixed", "income", "assets") mkString " " res12: String = Oversee fixed income assets scala> Vector("Oversee", "fixed", "income", "assets") mkString " " res13: String = Oversee fixed income assets scala> Array("Oversee", "fixed", "income", "assets") mkString ":" res14: String = Oversee:fixed:income:assets scala> xs.mkString(", ") res15: String = 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 scala> xs.mkString("; ") res16: String = 1; 2; 3; 4; 5; 6; 7; 8; 9; 10 scala> val rootDir = new java.io.File("/").listFiles().mkString("\n => ") rootDir: String = /etc => /tmp => /sbin => /sys => /opt => /media => /boot => /.local => /.autorelabel => /home
1.6.4 String conversion
String to Boolean
scala> "true".toBoolean res27: Boolean = true scala> "false".toBoolean res28: Boolean = false scala> "f".toBoolean java.lang.IllegalArgumentException: For input string: "f" at scala.collection.immutable.StringLike.parseBoolean(StringLike.scala:324)
String to signed Byte (8 bits from -128 to 127)
scala> "-128".toByte res37: Byte = -128 scala> "127".toByte res38: Byte = 127 scala> "128".toByte java.lang.NumberFormatException: Value out of range. Value:"128" Radix:10 at java.lang.Byte.parseByte(Byte.java:151) scala> "1df28".toByte java.lang.NumberFormatException: For input string: "1df28" at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65) at java.lang.Integer.parseInt(Integer.java:580) at java.lang.Byte.parseByte(Byte.java:149) at java.lang.Byte.parseByte(Byte.java:175)
String to Int
scala> "200".toInt res11: Int = 200 scala> "200".toInt * 3 res4: Int = 600 scala> 3 * "200".toInt res5: Int = 600 scala> 3 * "2ac00".toInt java.lang.NumberFormatException: For input string: "2ac00" .... ...
String to Long (64 bits Integer)
scala> "100".toLong res24: Long = 100 scala> "100fail!".toLong java.lang.NumberFormatException: For input string: "100fail!" at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65) at java.lang.Long.parseLong(Long.java:589) ... ...
String to Float (32-bits IEEE Float Point)
scala> "100".toFloat res21: Float = 100.0 scala> "100a".toFloat java.lang.NumberFormatException: For input string: "100a" at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:2043) at sun.misc.FloatingDecimal.parseFloat(FloatingDecimal.java:122) at java.lang.Float.parseFloat(Float.java:451)
String to Double (64-bits IEEE Float Point)
scala> "300".toDouble res7: Double = 300.0 scala> "300" toDouble res8: Double = 300.0 scala> Math.log10("100".toDouble) res10: Double = 2.0 scala> Math.log10("100It is gonna fail!".toDouble) java.lang.NumberFormatException: For input string: "100It is gonna fail!" at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:2043) at sun.misc.FloatingDecimal.parseDouble(FloatingDecimal.java:110) at java.lang.Double.parseDouble(Double.java:538) at scala.collection.immutable.StringLike.toDouble(StringLike.scala:318) at scala.collection.immutable.StringLike.toDouble$(StringLike.scala:318) at scala.collection.immutable.StringOps.toDouble(StringOps.scala:29) ... 29 elided
1.6.5 Regex - Regular Expressions
1.6.5.1 Pattern Matching Regex
scala> val dateRegex = """(\d\d\d\d)-(\d\d)-(\d\d)""".r dateRegex: scala.util.matching.Regex = (\d\d\d\d)-(\d\d)-(\d\d) "2010-10-15" match { case dateRegex(_*) => "It matches the date regex." } scala> "2010-10-15" match { | case dateRegex(_*) => "It matches the date regex." | } res1: String = It matches the date regex. "2010-10-15" match { case dateRegex(y, m, d) => s"The year = $y month = $m day = $d" } scala> "2010-10-15" match { | case dateRegex(y, m, d) => s"The year = $y month = $m day = $d" | } res0: String = The year = 2010 month = 10 day = 15
1.6.5.2 Extracting Regex
scala> val dateRegex = """(\d\d\d\d)-(\d\d)-(\d\d)""".r dateRegex: scala.util.matching.Regex = (\d\d\d\d)-(\d\d)-(\d\d) scala> val dateRegex(y, m, d) = "2010-10-15" y: String = 2010 m: String = 10 d: String = 15 scala> y res2: String = 2010 scala> m res3: String = 10 scala> d res4: String = 15 scala> m.toInt + 2 res5: Int = 12 scala> s"The year = $y month = $m day = $d" res6: String = The year = 2010 month = 10 day = 15 scala> println(s"The year = $y month = $m day = $d") The year = 2010 month = 10 day = 15
1.6.5.3 Find all strings that matching a regex
val dateRegex = """(\d\d\d\d)-(\d\d)-(\d\d)""".r scala> val dateList = "2004-01-20 1998-02-12 2003-04-15" dateList: String = 2004-01-20 1998-02-12 2003-04-15 scala> dateRegex.findFirstIn(dateList) res6: Option[String] = Some(2004-01-20) scala> m.next res9: scala.util.matching.Regex.Match = 2004-01-20 scala> m.next res10: scala.util.matching.Regex.Match = 1998-02-12 scala> m.next res11: scala.util.matching.Regex.Match = 2003-04-15 scala> m.hasNext res12: Boolean = false scala> m.next java.util.NoSuchElementException at scala.util.matching.Regex$MatchIterator.next(Regex.scala:753) at scala.util.matching.Regex$$anon$4.next(Regex.scala:374) at scala.util.matching.Regex$$anon$4.next(Regex.scala:371) ... 32 elided scala> val m = dateRegex.findAllMatchIn(dateList) m: Iterator[scala.util.matching.Regex.Match] = non-empty iterator scala> m.toList res14: List[scala.util.matching.Regex.Match] = List(2004-01-20, 1998-02-12, 2003-04-15)
1.6.5.4 Replace text
scala> val dateRegex = """(\d\d\d\d)-(\d\d)-(\d\d)""".r dateRegex: scala.util.matching.Regex = (\d\d\d\d)-(\d\d)-(\d\d) scala> val dateList = "2004-01-20 1998-02-12 2003-04-15" dateList: String = 2004-01-20 1998-02-12 2003-04-15 scala> dateRegex.replaceAllIn(dateList, "xxxx-xx-xx") res16: String = xxxx-xx-xx xxxx-xx-xx xxxx-xx-xx
1.6.5.5 Regex use case for extracting color from string
val rgbRegex = """rgb\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)""".r def getColor(color: String) = color match { case "blue" => java.awt.Color.blue case "cyan" => java.awt.Color.cyan case "red" => java.awt.Color.red case "green" => java.awt.Color.green case "yellow" => java.awt.Color.yellow case "white" => java.awt.Color.white case "pink" => java.awt.Color.pink case "black" => java.awt.Color.black case "magenta" => java.awt.Color.magenta case "orange" => java.awt.Color.orange case "gray" => java.awt.Color.gray case rgbRegex(r, g, b) => new java.awt.Color(r.toInt, g.toInt, b.toInt) case _ => throw new IllegalArgumentException("Error invalid color name.") } @ getColor("blue") res2: java.awt.Color = java.awt.Color[r=0,g=0,b=255] @ @ getColor("rgb(255, 0, 0)") res3: java.awt.Color = java.awt.Color[r=255,g=0,b=0] @ @ getColor("rgb(10, 121, 25)") res4: java.awt.Color = java.awt.Color[r=10,g=121,b=25] @ @ getColor("rgb(10, 121, 25) error") java.lang.IllegalArgumentException: Error invalid color name. ammonite.$sess.cmd1$.getColor(cmd1.sc:14) ammonite.$sess.cmd5$.<init>(cmd5.sc:1) ammonite.$sess.cmd5$.<clinit>(cmd5.sc) @ getColor("red") res6: java.awt.Color = java.awt.Color[r=255,g=0,b=0] @ @ getColor("invalid color") java.lang.IllegalArgumentException: Error invalid color name. ammonite.$sess.cmd1$.getColor(cmd1.sc:14) ammonite.$sess.cmd7$.<init>(cmd7.sc:1) ammonite.$sess.cmd7$.<clinit>(cmd7.sc)
1.6.5.6 Documentation and References
1.7 Collections
1.7.1 Overview
Collection Hierarchy
- Iterable
- Seq (Sequence)
- List
- Fundamental operations: head, tail
- Vector
- indexing
- Array. Mutable array, equivalent to Java Array.
- String (Seq-like, although not subclass of Seq).
- Range
- List
- Sets (Relational algebra). Contains no duplicated element.
- Map (aka Hashmap, Dictionary or hash-table)
- Seq (Sequence)
Scala Collection | Description | Immutable |
---|---|---|
List | Linked list | Yes |
Iterable / Stream | Lazy evaluation | Yes |
Array | Random Access by index | No |
Map | Hash table / Dictionary, Index table | Yes |
Set | Unique items | Yes |
1.7.2 Immutable Collections
1.7.2.1 Tuples
1.7.2.2 List
Creating a list
scala> var xs = List(1.0, 2.0, 3.0, 4.0, 5.0, 6.0) xs: List[Double] = List(1.0, 2.0, 3.0, 4.0, 5.0, 6.0)
Map over a list
scala> xs.map (x => x * 3.0) res31: List[Double] = List(3.0, 6.0, 9.0, 12.0, 15.0, 18.0) scala> xs.map (x => x * 3.0).map (x => x + 5) res33: List[Double] = List(8.0, 11.0, 14.0, 17.0, 20.0, 23.0)
Filter a list
// Filter // scala> xs.filter ( x => x < 4.0) res30: List[Double] = List(1.0, 2.0, 3.0)
Filter a list / reject
// FilterNot - Inverse of filter, reject // scala> xs.filterNot (x => x < 4.0) res80: List[Double] = List(4.0, 5.0, 6.0)
Find a element that matches a predicate function
// Find the first element that satisfies // a predicate. // // scala> xs.find _ res43: (Double => Boolean) => Option[Double] = <function1> scala> xs.find (x => x > 4.0) res42: Option[Double] = Some(5.0) scala> xs.find (x => x > 14.0) res44: Option[Double] = None
Test if list is empty
// Test if list is empty // scala> xs.isEmpty res85: Boolean = false
Find the index of an element that satisfies a predicate
// Find the index of an element that satisfies a predicate. // // scala> xs.indexWhere (x => x > 4.0) res116: Int = 4 scala> xs.indexWhere (x => x > 14.0) res117: Int = -1
Count all elements that matches a predicate
// Count all elements greater than 3.0 // scala> xs.count (x => x > 3.0) res18: Int = 3
Get max and min elements
// Max and Min elements // scala> xs.max res19: Double = 6.0 scala> xs.min res20: Double = 1.0
Head (fist) and (last) elements
// Head and tail of a list. // First element scala> xs.head res21: Double = 1.0 // Last element scala> xs.last res45: Double = 6.0
Tail
// // Tail: Remove first element scala> xs.tail res22: List[Double] = List(2.0, 3.0, 4.0, 5.0, 6.0)
Reverse a list
scala> xs.reverse res36: List[Double] = List(6.0, 5.0, 4.0, 3.0, 2.0, 1.0)
Foreach
// Impure Map // scala> xs.foreach(println) 1.0 2.0 3.0 4.0 5.0 6.0 scala> xs.foreach(x => println( "x = %.3f".format(x))) x = 1,000 x = 2,000 x = 3,000 x = 4,000 x = 5,000 x = 6,000
Slice elements
// Select elements x[2],x[3] and x[4] // scala> xs.slice(2, 5) res40: List[Double] = List(3.0, 4.0, 5.0)
Take n elements
scala> xs.take(3) res68: List[Double] = List(1.0, 2.0, 3.0)
Drop elements
// Drop elements // scala> xs.drop _ res66: Int => List[Double] = <function1> scala> xs.drop (3) res67: List[Double] = List(4.0, 5.0, 6.0)
Length of a list
// Length of a list // scala> xs.length res69: Int = 6
Sum of all list elements
// Sum of all elements of a list // scala> xs.sum res82: Double = 21.0
Product of all list elements
// Product of all elements of a list // scala> xs.product res83: Double = 720.0
Fold left
// Fold left // scala> List(1, 2, 3, 4, 5).foldLeft(0)((acc, x) => 100 * acc + x) res107: Int = 102030405 scala> List(1, 2, 3, 4, 5).foldLeft(List[Int] ())((acc, x) => x :: acc) res110: List[Int] = List(5, 4, 3, 2, 1)
Fold right
// Fold right // scala> List(1, 2, 3, 4, 5).foldRight(0)((x, acc) => 10 * acc + x) res111: Int = 54321
Reduce
// Reduce. fold left without initial value of accumulator. scala> xs.reduce _ res92: ((Double, Double) => Double) => Double = <function1> scala> xs.reduce ((acc, x) => 10*acc + x) res95: Double = 123456.0
Max by
// Returns the element for which the projection function has the // maximun value // // In this case: returns the string which its lenght is maximun. // scala> var s = List("Hello", "World", "Scala", "is", "amazing") s: List[String] = List(Hello, World, Scala, is, amazing) scala> s.maxBy (x => x.length) res74: String = amazing
Min by
// // In this case: returns the string which its length is minimun. // scala> s.minBy (x => x.length) res75: String = is
Sort by
// Sort the string by the length of each string // scala> s.sortBy ( x => x.length) res78: List[String] = List(is, Hello, World, Scala, amazing)
Group by
// groupBy // Separate string that have equal number of characters // scala> s.groupBy(x => x.length) res0: scala.collection.immutable.Map[Int,List[String]] = Map(2 -> List(is), 5 -> List(Hello, World, Scala), 7 -> List(amazing)) def fileExtension (filename: String) = { val arr = filename.split ('.'); if (arr.length > 1) { arr.apply(1); }else{ ""; } } var files = List("file1.pdf", "file2.doc", "dummy.pdf", "clojure.jar", "document.zip", "file3.pdf", "scala.jar", "manifest.doc", "unixBsd" ) scala> files.groupBy (fileExtension) res17: scala.collection.immutable.Map[String,List[String]] = Map("" -> List(unixBsd), zip -> List(document.zip), pdf -> List(file1.pdf, dummy.pdf, file3.pdf), doc -> List(file2.doc, manifest.doc), jar -> List(clojure.jar, scala.jar)) scala> files.groupBy (fileExtension).foreach(println) (,List(unixBsd)) (zip,List(document.zip)) (pdf,List(file1.pdf, dummy.pdf, file3.pdf)) (doc,List(file2.doc, manifest.doc)) (jar,List(clojure.jar, scala.jar))
Distinct
// Distinct elements. // scala> var a = List(1, 2, 5, 3, 1, 3, 3, 5, 4, 5, 4) a: List[Int] = List(1, 2, 5, 3, 1, 3, 3, 5, 4, 5, 4) scala> a.distinct res88: List[Int] = List(1, 2, 5, 3, 4)
1.7.2.3 Maps
Scala Maps are immutable hash tables or dictionaries.
- Scala Maps methods signatures.
var capital = Map("US" -> "Washigton", "France" -> "Paris", "Japan" -> "Tokyo") scala> capital("Japan") res8: String = Tokyo scala> capital("US") res9: String = Washigton scala> capital("USsa") java.util.NoSuchElementException: key not found: USsa at scala.collection.MapLike$class.default(MapLike.scala:228) at scala.collection.AbstractMap.default(Map.scala:59) at scala.collection.MapLike$class.apply(MapLike.scala:141) at scala.collection.AbstractMap.apply(Map.scala:59) ... 32 elided scala> assert(capital("Japan") == "Tokyo") scala> assert(capital("Japan") == "Tokyo2") java.lang.AssertionError: assertion failed at scala.Predef$.assert(Predef.scala:156) ... 32 elided scala> println(capital("France")) Paris scala> println(capital("Japan")) Tokyo
def getFileExtension(file: String) = { val i = file.lastIndexOf('.') if ( i > 0) file.substring(i+1) else "" } // it creates a Map of file extensions as keys and // all files with given extension as values // val fileGroups = { (new java.io.File("/etc/")) .listFiles() .filter(_.isFile) .map(_.getPath) .groupBy(getFileExtension) } scala> val fileGroups = { | (new java.io.File("/etc/")) | .listFiles() | .filter(_.isFile) | .map(_.getPath) | .groupBy(getFileExtension) | } fileGroups: scala.collection.immutable.Map[String,Array[String]] = Map("" -> Array(/etc/motd, /etc/gemrc, /etc/environment, /etc/gshadow, /etc/shadow-, /etc/fstab, /etc/rpc, /etc/passwd, /etc/gnome-vfs-mime-magic, /etc/passwd-, /etc/sudoers, /etc/anacrontab, /etc/os-release, /etc/adjtime, /etc/netconfig, /etc/inputrc, /etc/timezone, /etc/shadow, /etc/lsb-release, /etc/shells, /etc/papersize, /etc/drirc, /etc/hostname, /etc/exports, /etc/machine-id, /etc/group-, /etc/nanorc, /etc/hosts, /etc/group, /etc/mtab, /etc/securetty, /etc/services, /etc/protocols, /etc/gshadow-, /etc/localtime, /etc/issue, /etc/ethertypes, /etc/manjaro-release, /etc/yaourtrc, /etc/profile, /etc/printcap, /etc/crypttab), backup -> Array(/etc/pacman-mirrors.conf.20170402.backup), bash_logout -> Array(/etc/bash.bash... scala> // Get all files with cfg extension // scala> fileGroups("cfg") res113: Array[String] = Array(/etc/vdpau_wrapper.cfg, /etc/rc_maps.cfg) // Get all files with conf extension // cala> fileGroups("conf") res117: Array[String] = Array(/etc/sensors3.conf, /etc/pacman.conf, /etc/cpufreq-bench.conf, /etc/makepkg.conf, /etc/ld.so.conf, /etc/host.conf, /etc/healthd.conf, /etc/ts.conf, /etc/resolvconf.conf, /etc/logrotate.conf, /etc/locale.conf, /etc/request-key.conf, /etc/nscd.conf, /etc/dnsmasq.conf, /etc/nsswitch.conf, /etc/ntp.conf, /etc/updatedb.conf, /etc/dhcpcd.conf, /etc/krb5.conf, /etc/openswap.conf, /etc/vconsole.conf, /etc/mkinitcpio.conf, /etc/man_db.conf, /etc/mke2fs.conf, /etc/fuse.conf, /etc/asound.conf, /etc/mdadm.conf, /etc/pamac.conf, /etc/nfs.conf, /etc/nfsmount.conf, /etc/resolv.conf, /etc/gai.conf, /etc/pacman-mirrors.conf, /etc/rsyncd.conf) scala> fileGroups("wrong") java.util.NoSuchElementException: key not found: wrong at scala.collection.MapLike$class.default(MapLike.scala:228) at scala.collection.AbstractMap.default(Map.scala:59) at scala.collection.MapLike$class.apply(MapLike.scala:141) at scala.collection.AbstractMap.apply(Map.scala:59) ... 32 elided
Method: .get -> Get a element with given key, returning None if not element is found.
scala> fileGroups.get("conf") res119: Option[Array[String]] = Some([Ljava.lang.String;@4cb04f41) scala> fileGroups.get("conf").map(_.toList) res123: Option[List[String]] = Some(List(/etc/sensors3.conf, /etc/pacman.conf, /etc/cpufreq-bench.conf, /etc/makepkg.conf, /etc/ld.so.conf, /etc/host.conf, /etc/healthd.conf, /etc/ts.conf, /etc/resolvconf.conf, /etc/logrotate.conf, /etc/locale.conf, /etc/request-key.conf, /etc/nscd.conf, /etc/dnsmasq.conf, /etc/nsswitch.conf, /etc/ntp.conf, /etc/updatedb.conf, /etc/dhcpcd.conf, /etc/krb5.conf, /etc/openswap.conf, /etc/vconsole.conf, /etc/mkinitcpio.conf, /etc/man_db.conf, /etc/mke2fs.conf, /etc/fuse.conf, /etc/asound.conf, /etc/mdadm.conf, /etc/pamac.conf, /etc/nfs.conf, /etc/nfsmount.conf, /etc/resolv.conf, /etc/gai.conf, /etc/pacman-mirrors.conf, /etc/rsyncd.conf)) scala> fileGroups.get("confx") res125: Option[Array[String]] = None
Method: .head -> Get first element of Map
scala> fileGroups.head res143: (String, Array[String]) = ("",Array(/etc/motd, /etc/gemrc, /etc/environment, /etc/gshadow, /etc/shadow-, /etc/fstab, /etc/rpc, /etc/passwd, /etc/gnome-vfs-mime-magic, /etc/passwd-, /etc/sudoers, /etc/anacrontab, /etc/os-release, /etc/adjtime, /etc/netconfig, /etc/inputrc, /etc/timezone, /etc/shadow, /etc/lsb-release, /etc/shells, /etc/papersize, /etc/drirc, /etc/hostname, /etc/exports, /etc/machine-id, /etc/group-, /etc/nanorc, /etc/hosts, /etc/group, /etc/mtab, /etc/securetty, /etc/services, /etc/protocols, /etc/gshadow-, /etc/localtime, /etc/issue, /etc/ethertypes, /etc/manjaro-release, /etc/yaourtrc, /etc/profile, /etc/printcap, /etc/crypttab))
Method: .last -> Get last element of Map
scala> fileGroups.last res145: (String, Array[String]) = (defs,Array(/etc/login.defs))
Method: .key -> Get all keys
// Get all file extensions scala> fileGroups.keys res114: Iterable[String] = Set("", backup, bash_logout, local, pacnew, lock, conf, cache, key, shutdown, updated, cfg, deny, bashrc, types, rc, gen, defs) // Print all file extensions scala> fileGroups.keys.foreach(println) backup bash_logout local pacnew lock conf cache key shutdown updated cfg deny bashrc types rc gen defs
Method: .values -> Get all values.
scala> fileGroups.values res126: Iterable[Array[String]] = MapLike(Array(/etc/motd, /etc/gemrc, /etc/environment, /etc/gshadow, /etc/shadow-, /etc/fstab, /etc/rpc, /etc/passwd, /etc/gnome-vfs-mime-magic, /etc/passwd-, /etc/sudoers, /etc/anacrontab, /etc/os-release, /etc/adjtime, /etc/netconfig, /etc/inputrc, /etc/timezone, /etc/shadow, /etc/lsb-release, /etc/shells, /etc/papersize, /etc/drirc, /etc/hostname, /etc/exports, /etc/machine-id, /etc/group-, /etc/nanorc, /etc/hosts, /etc/group, /etc/mtab, /etc/securetty, /etc/services, /etc/protocols, /etc/gshadow-, /etc/localtime, /etc/issue, /etc/ethertypes, /etc/manjaro-release, /etc/yaourtrc, /etc/profile, /etc/printcap, /etc/crypttab), Array(/etc/pacman-mirrors.conf.20170402.backup), Array(/etc/bash.bash_logout), Array(/etc/rc.local), Array(/etc/pacman-mirrors.co... scala> scala> fileGroups.values.head res129: Array[String] = Array(/etc/motd, /etc/gemrc, /etc/environment, /etc/gshadow, /etc/shadow-, /etc/fstab, /etc/rpc, /etc/passwd, /etc/gnome-vfs-mime-magic, /etc/passwd-, /etc/sudoers, /etc/anacrontab, /etc/os-release, /etc/adjtime, /etc/netconfig, /etc/inputrc, /etc/timezone, /etc/shadow, /etc/lsb-release, /etc/shells, /etc/papersize, /etc/drirc, /etc/hostname, /etc/exports, /etc/machine-id, /etc/group-, /etc/nanorc, /etc/hosts, /etc/group, /etc/mtab, /etc/securetty, /etc/services, /etc/protocols, /etc/gshadow-, /etc/localtime, /etc/issue, /etc/ethertypes, /etc/manjaro-release, /etc/yaourtrc, /etc/profile, /etc/printcap, /etc/crypttab)
Method: .size -> Get the number of Map elements or the number of key, value pairs.
scala> fileGroups.size res148: Int = 18 scala> fileGroups size res149: Int = 18
Method: .mapValues -> Apply a function to each map value.
/// Get a Map with all extension and number of files with a given extension. scala> fileGroups.mapValues(x => x.length) res136: scala.collection.immutable.Map[String,Int] = Map("" -> 42, backup -> 1, bash_logout -> 1, local -> 1, pacnew -> 1, lock -> 1, conf -> 34, cache -> 1, key -> 1, shutdown -> 1, updated -> 1, cfg -> 2, deny -> 1, bashrc -> 1, types -> 1, rc -> 2, gen -> 1, defs -> 1) scala> fileGroups.mapValues(_.length) res137: scala.collection.immutable.Map[String,Int] = Map("" -> 42, backup -> 1, bash_logout -> 1, local -> 1, pacnew -> 1, lock -> 1, conf -> 34, cache -> 1, key -> 1, shutdown -> 1, updated -> 1, cfg -> 2, deny -> 1, bashrc -> 1, types -> 1, rc -> 2, gen -> 1, defs -> 1) scala> fileGroups.mapValues(_.length).foreach(println) (,42) (backup,1) (bash_logout,1) (local,1) (pacnew,1) (lock,1) (conf,34) (cache,1) (key,1) (shutdown,1) (updated,1) (cfg,2) (deny,1) (bashrc,1) (types,1) (rc,2) (gen,1) (defs,1)
Method: .map -> Apply a function to each to each (key, value) pair.
scala> fileGroups.map {case (k, v) => (k, v.length)} // Get new Map with (extension, number of files with extension) // pairs. // res160: scala.collection.immutable.Map[String,Int] = Map("" -> 42, backup -> 1, bash_logout -> 1, local -> 1, pacnew -> 1, lock -> 1, conf -> 34, cache -> 1, key -> 1, shutdown -> 1, updated -> 1, cfg -> 2, deny -> 1, bashrc -> 1, types -> 1, rc -> 2, gen -> 1, defs -> 1) scala> fileGroups.map {case (k, v) => (k, v.max)} res161: scala.collection.immutable.Map[String,String] = Map("" -> /etc/yaourtrc, backup -> /etc/pacman-mirrors.conf.20170402.backup, bash_logout -> /etc/bash.bash_logout, local -> /etc/rc.local, pacnew -> /etc/pacman-mirrors.conf.pacnew, lock -> /etc/.pwd.lock, conf -> /etc/vconsole.conf, cache -> /etc/ld.so.cache, key -> /etc/trusted-key.key, shutdown -> /etc/rc.local.shutdown, updated -> /etc/.updated, cfg -> /etc/vdpau_wrapper.cfg, deny -> /etc/cron.deny, bashrc -> /etc/bash.bashrc, types -> /etc/mime.types, rc -> /etc/slsh.rc, gen -> /etc/locale.gen, defs -> /etc/login.defs)
Method: .foreach -> Apply a function that returns Unit (returns no value or void) to each element.
fileGroups.foreach {case (k, v) => printf("Number of files with extenson\t'%s'\t\t=%d\n", k, v.length)} scala> fileGroups.foreach {case (k, v) => println(k)} backup bash_logout local pacnew lock conf cache key shutdown updated cfg deny bashrc types rc gen defs scala> fileGroups.foreach {case (k, v) => println(k, v.length)} (,42) (backup,1) (bash_logout,1) (local,1) (pacnew,1) (lock,1) (conf,34) (cache,1) (key,1) (shutdown,1) (updated,1) (cfg,2) (deny,1) (bashrc,1) (types,1) (rc,2) (gen,1) (defs,1) scala> fileGroups.mapValues(_.length).foreach(println) (,42) (backup,1) (bash_logout,1) (local,1) (pacnew,1) (lock,1) (conf,34) (cache,1) (key,1) (shutdown,1) (updated,1) (cfg,2) (deny,1) (bashrc,1) (types,1) (rc,2) (gen,1) (defs,1) scala> fileGroups.foreach {case (k, v) => printf("Number of files with extenson\t'%s'\t\t=%d\n", k, v.length)} Number of files with extenson '' =42 Number of files with extenson 'backup' =1 Number of files with extenson 'bash_logout' =1 Number of files with extenson 'local' =1 Number of files with extenson 'pacnew' =1 Number of files with extenson 'lock' =1 Number of files with extenson 'conf' =34 Number of files with extenson 'cache' =1 Number of files with extenson 'key' =1 Number of files with extenson 'shutdown' =1 Number of files with extenson 'updated' =1 Number of files with extenson 'cfg' =2 Number of files with extenson 'deny' =1 Number of files with extenson 'bashrc' =1 Number of files with extenson 'types' =1 Number of files with extenson 'rc' =2 Number of files with extenson 'gen' =1 Number of files with extenson 'defs' =1
Method: .isEmpty -> Test if Map is empty.
scala> fileGroups.isEmpty res151: Boolean = false
Method: .toList -> Convert a Map to a list of key and values.
scala> val fileCounts = fileGroups.mapValues(_.length) fileCounts: scala.collection.immutable.Map[String,Int] = Map("" -> 42, backup -> 1, bash_logout -> 1, local -> 1, pacnew -> 1, lock -> 1, conf -> 34, cache -> 1, key -> 1, shutdown -> 1, updated -> 1, cfg -> 2, deny -> 1, bashrc -> 1, types -> 1, rc -> 2, gen -> 1, defs -> 1) scala> fileGroups.toList scala> fileCounts.toList res140: List[(String, Int)] = List(("",42), (backup,1), (bash_logout,1), (local,1), (pacnew,1), (lock,1), (conf,34), (cache,1), (key,1), (shutdown,1), (updated,1), (cfg,2), (deny,1), (bashrc,1), (types,1), (rc,2), (gen,1), (defs,1))
Method: .toArray -> Convert a Map to an array of key and values.
scala> val fileCounts = fileGroups.mapValues(_.length) scala> fileCounts.toArray res141: Array[(String, Int)] = Array(("",42), (backup,1), (bash_logout,1), (local,1), (pacnew,1), (lock,1), (conf,34), (cache,1), (key,1), (shutdown,1), (updated,1), (cfg,2), (deny,1), (bashrc,1), (types,1), (rc,2), (gen,1), (defs,1))
1.7.3 Mutable Collections
1.7.3.1 Array
- Scala Arrays methods signatures.
scala> val arr = Array(1, 2, 3, 4, 5, 6) arr: Array[Int] = Array(1, 2, 3, 4, 5, 6) // Type tab after the dot to show the Array methods scala> arr. ++ filterNot maxBy span ++: find min splitAt +: flatMap minBy startsWith /: flatten mkString stringPrefix :+ fold nonEmpty sum :\ foldLeft orElse tail addString foldRight padTo tails aggregate forall par take andThen foreach partition takeRight apply genericBuilder patch takeWhile applyOrElse groupBy permutations to array grouped prefixLength toArray canEqual hasDefiniteSize product toBuffer clone head reduce toIndexedSeq collect headOption reduceLeft toIterable collectFirst indexOf reduceLeftOption toIterator combinations indexOfSlice reduceOption toList companion indexWhere reduceRight toMap compose indices reduceRightOption toSeq contains init repr toSet containsSlice inits reverse toStream copyToArray intersect reverseIterator toTraversable copyToBuffer isDefinedAt reverseMap toVector corresponds isEmpty runWith transform count isTraversableAgain sameElements transpose deep iterator scan union diff last scanLeft unzip distinct lastIndexOf scanRight unzip3 drop lastIndexOfSlice segmentLength update dropRight lastIndexWhere seq updated dropWhile lastOption size view elemManifest length slice withFilter elemTag lengthCompare sliding zip endsWith lift sortBy zipAll exists map sortWith zipWithIndex filter max sorted
Get first and last elements.
scala> val arr = Array(3.4, 2.5, -4.5, 4.0, 5.0, -6.31) arr: Array[Double] = Array(3.4, 2.5, -4.5, 4.0, 5.0, -6.31) scala> arr.head res10: Double = 3.4 scala> arr.tail res11: Array[Double] = Array(2.5, -4.5, 4.0, 5.0, -6.31)
Get array tail (remove first element)
scala> val arr = Array(3.4, 2.5, -4.5, 4.0, 5.0, -6.31) arr: Array[Double] = Array(3.4, 2.5, -4.5, 4.0, 5.0, -6.31) scala> arr.tail res25: Array[Double] = Array(2.5, -4.5, 4.0, 5.0, -6.31) scala> arr res26: Array[Double] = Array(3.4, 2.5, -4.5, 4.0, 5.0, -6.31)
Get nth-element.
scala> val arr = Array(3.4, 2.5, -4.5, 4.0, 5.0, -6.31) arr: Array[Double] = Array(3.4, 2.5, -4.5, 4.0, 5.0, -6.31) scala> arr(0) res12: Double = 3.4 scala> arr(1) res13: Double = 2.5 scala> arr(4) res14: Double = 5.0 scala> arr(10) java.lang.ArrayIndexOutOfBoundsException: 10 ... 32 elided
Change nth-element.
scala> val arr = Array(3.4, 2.5, -4.5, 4.0, 5.0, -6.31) arr: Array[Double] = Array(3.4, 2.5, -4.5, 4.0, 5.0, -6.31) scala> arr(3) res0: Double = 4.0 scala> arr(3) = 100.0 scala> arr res2: Array[Double] = Array(3.4, 2.5, -4.5, 100.0, 5.0, -6.31) scala> arr(0) res3: Double = 3.4 scala> arr(0) = 5.0 scala> arr res5: Array[Double] = Array(5.0, 2.5, -4.5, 100.0, 5.0, -6.31) scala> arr(0) res6: Double = 5.0
Get minimum and maximum elements.
scala> arr res18: Array[Double] = Array(3.4, 2.5, -4.5, 4.0, 5.0, -6.31) scala> arr.max res19: Double = 5.0 scala> arr.min res20: Double = -6.31
Get array length.
scala> val arr = Array(1, 2, 3, 4, 5, 6) arr: Array[Int] = Array(1, 2, 3, 4, 5, 6) scala> arr.length length lengthCompare scala> arr.length res6: Int = 6
Reverse array.
scala> val arr = Array(3.4, 2.5, -4.5, 4.0, 5.0, -6.31) arr: Array[Double] = Array(3.4, 2.5, -4.5, 4.0, 5.0, -6.31) scala> arr.reverse res16: Array[Double] = Array(-6.31, 5.0, 4.0, -4.5, 2.5, 3.4) scala> arr res17: Array[Double] = Array(3.4, 2.5, -4.5, 4.0, 5.0, -6.31)
Convert Array to List.
scala> val arr = Array(3.4, 2.5, -4.5, 4.0, 5.0, -6.31) arr: Array[Double] = Array(3.4, 2.5, -4.5, 4.0, 5.0, -6.31) scala> arr.toList res21: List[Double] = List(3.4, 2.5, -4.5, 4.0, 5.0, -6.31) scala> arr res22: Array[Double] = Array(3.4, 2.5, -4.5, 4.0, 5.0, -6.31)
Get array sum:
scala> val arr = Array(1, 2, 3, 4, 5, 6) arr: Array[Int] = Array(1, 2, 3, 4, 5, 6) scala> arr.sum res7: Int = 21
Get array product:
scala> val arr = Array(3.4, 2.5, -4.5, 4.0, 5.0, -6.31) arr: Array[Double] = Array(3.4, 2.5, -4.5, 4.0, 5.0, -6.31) scala> arr.product res9: Double = 4827.15
Map - Apply a function to all array elements.
scala> val arr = Array(3, 2, -4, 4, 5, -6) arr: Array[Int] = Array(3, 2, -4, 4, 5, -6) // Map with anonymous function //-------------------------------------------------- scala> arr.map (x => x * 3) res30: Array[Int] = Array(9, 6, -12, 12, 15, -18) scala> arr map (x => x * 3) res35: Array[Int] = Array(9, 6, -12, 12, 15, -18) // Map a function // scala> def fn(x: Int) = x * 2 - 5 fn: (x: Int)Int scala> arr.map(fn) res34: Array[Int] = Array(1, -1, -13, 3, 5, -17) scala> arr map fn res36: Array[Int] = Array(1, -1, -13, 3, 5, -17) // Convert integer to double scala> 10 res41: Int = 10 scala> 10.toDouble res42: Double = 10.0 // Map a function that applies a method. //-------------------------------------------------- scala> arr map (_.toDouble) res43: Array[Double] = Array(3.0, 2.0, -4.0, 4.0, 5.0, -6.0) scala> arr map (_.toString) res54: Array[String] = Array(3, 2, -4, 4, 5, -6) scala> arr map (_.toHexString) res58: Array[String] = Array(3, 2, fffffffc, 4, 5, fffffffa) // Math syntax sugars //----------------------------------------------- scala> arr map (_ + 10) res44: Array[Int] = Array(13, 12, 6, 14, 15, 4) scala> arr map (_ * 10) res45: Array[Int] = Array(30, 20, -40, 40, 50, -60) scala> arr map (_ * 10) map (5 + _) res49: Array[Int] = Array(35, 25, -35, 45, 55, -55) scala> arr map (_ * 10) sum res48: Int = 40 scala> arr res51: Array[Int] = Array(3, 2, -4, 4, 5, -6) // 13 = 16 - 3 // 14 = 16 - 2 // 20 = 16 -(-4) // ... ... scala> arr map (16 - _) res52: Array[Int] = Array(13, 14, 20, 12, 11, 22)
Filter - an array. Select all array elements that satisfies a predicate.
scala> val arr = Array(3, 2, -4, 4, 5, -6) arr: Array[Int] = Array(3, 2, -4, 4, 5, -6) scala> arr.filter(x => x > 0) res68: Array[Int] = Array(3, 2, 4, 5 scala> arr filter (x => x > 0) res70: Array[Int] = Array(3, 2, 4, 5) scala> arr filter (x => x > 0) sum res71: Int = 14 scala> arr filter (x => x > 0) product res72: Int = 120 scala> arr.filter(_ > 0) res74: Array[Int] = Array(3, 2, 4, 5) scala> arr.filter(_ < 0) res77: Array[Int] = Array(-4, -6) scala> arr filter (_ < 0) res78: Array[Int] = Array(-4, -6)
Reduce (fold). It fails for empty arrays.
- acc stands for accumulator.
scala> val arr = Array(1, 2, 3, 4, 5, 6) arr: Array[Int] = Array(1, 2, 3, 4, 5, 6) // sum of array elements scala> arr.reduce((acc, x) => acc + x) res12: Int = 21 scala> arr.sum res15: Int = 21 // product of array elements scala> arr.reduce((acc, x) => acc * x) res16: Int = 720 scala> arr.product res17: Int = 720 scala> arr.reduce((acc, x) => 10 * acc + x) res18: Int = 123456 scala> arr reduce((acc, x) => acc * x) res21: Int = 720 scala> val emptyArr: Array[Double] = Array() emptyArr: Array[Double] = Array() scala> emptyArr.reduce((acc, x) => 10 * acc + x) java.lang.UnsupportedOperationException: empty.reduceLeft at scala.collection.TraversableOnce$class.reduceLeft(TraversableOnce.scala:180) at scala.collection.mutable.ArrayOps$ofDouble.scala$collection$IndexedSeqOptimized$$super$reduceLeft(ArrayOps.scala:270) at scala.collection.IndexedSeqOptimized$class.reduceLeft(IndexedSeqOptimized.scala:74) at scala.collection.mutable.ArrayOps$ofDouble.reduceLeft(ArrayOps.scala:270) at scala.collection.TraversableOnce$class.reduce(TraversableOnce.scala:208) at scala.collection.mutable.ArrayOps$ofDouble.reduce(ArrayOps.scala:270) ... 32 elided
foldLeft - Like reduce, but it works for empty arrays.
scala> val arr = Array(1, 2, 3, 4, 5, 6) arr: Array[Int] = Array(1, 2, 3, 4, 5, 6) scala> val emptyArr : Array[Int] = Array() emptyArr: Array[Int] = Array() scala> arr.foldLeft(0)((acc, x) => 10 * acc + x) res30: Int = 123456 scala> emptyArr.foldLeft(0)((acc, x) => 10 * acc + x) res33: Int = 0 scala> arr.foldLeft(1)((acc, x) => acc * x) res38: Int = 720 scala> emptyArr.foldLeft(1)((acc, x) => acc * x) res39: Int = 1 scala> arr.foldLeft(())((_, x) => println(x)) 1 2 3 4 5 6 // - '()' - Unit type // scala> emptyArr.foldLeft(())((_, x) => println(x)) scala> () scala>
foldRight
scala> val arr = Array(1, 2, 3, 4, 5, 6) arr: Array[Int] = Array(1, 2, 3, 4, 5, 6) scala> val emptyArr : Array[Int] = Array() emptyArr: Array[Int] = Array() scala> arr.foldRight(0)((x, acc) => 10* acc + x) res46: Int = 654321 scala> emptyArr.foldRight(0)((x, acc) => 10* acc + x) res47: Int = 0 scala> arr.foldRight(0)((x, acc) => acc + x) res49: Int = 21 scala> arr.foldRight(1)((x, acc) => acc * x) res50: Int = 720
Group By
- groupBy[k](f: Element => key) : Map[key, Array[Element]]
def getFileExtension(file: String) = { val i = file.lastIndexOf('.') if ( i > 0) file.substring(i+1) else "" } val flist = List( "/downloads/magazine.pdf" ,"afile.html" ,"file2.html" ,"file3.png" ,"config" ,"imageFun.png" ,"unix.pdf" ,"script10.scala" ,"bashrc" ) scala> val flist = List( | "/downloads/magazine.pdf" | ,"afile.html" | ,"file2.html" | ,"file3.png" | ,"config" | ,"imageFun.png" | ,"unix.pdf" | ,"script10.scala" | ,"bashrc" | ) flist: List[String] = List(/downloads/magazine.pdf, afile.html, file2.html, file3.png, config, imageFun.png, unix.pdf, script10.scala, bashrc) scala> flist map getFileExtension res63: List[String] = List(pdf, html, html, png, "", png, pdf, scala, "") scala> val fgroups = flist.groupBy(getFileExtension) fgroups: scala.collection.immutable.Map[String,List[String]] = Map("" -> List(config, bashrc), png -> List(file3.png, imageFun.png), pdf -> List(/downloads/magazine.pdf, unix.pdf), scala -> List(script10.scala), html -> List(afile.html, file2.html)) // Get extensions // scala> fgroups.keys res74: Iterable[String] = Set("", png, pdf, scala, html) scala> fgroups.keys.foreach(println) png pdf scala html // Get all files without extension // scala> fgroups.get("") res75: Option[List[String]] = Some(List(config, bashrc)) scala> fgroups.get("").get res79: List[String] = List(config, bashrc) scala> fgroups.get("tgz") res81: Option[List[String]] = None scala> fgroups.get("tgz").get java.util.NoSuchElementException: None.get at scala.None$.get(Option.scala:347) at scala.None$.get(Option.scala:345) ... 32 elided // Get all files with extension *.png scala> fgroups.get("png") res76: Option[List[String]] = Some(List(file3.png, imageFun.png)) scala> var files = (new java.io.File("/etc/")).listFiles().filter(_.isFile) files: Array[java.io.File] = Array(/etc/motd, /etc/gemrc, /etc/ld.so.cache, /etc/environment, /etc/sensors3.conf, /etc/gshadow, /etc/cron.deny, /etc/shadow-, /etc/vdpau_wrapper.cfg, /etc/pacman.conf, /etc/cpufreq-bench.conf, /etc/makepkg.conf, /etc/ld.so.conf, /etc/fstab, /etc/host.conf, /etc/rpc, /etc/mime.types, /etc/locale.gen, /etc/passwd, /etc/healthd.conf, /etc/gnome-vfs-mime-magic, /etc/ts.conf, /etc/resolvconf.conf, /etc/passwd-, /etc/logrotate.conf, /etc/locale.conf, /etc/pacman-mirrors.conf.20170402.backup, /etc/login.defs, /etc/sudoers, /etc/request-key.conf, /etc/bash.bashrc, /etc/anacrontab, /etc/nscd.conf, /etc/os-release, /etc/adjtime, /etc/dnsmasq.conf, /etc/netconfig, /etc/mail.rc, /etc/inputrc, /etc/nsswitch.conf, /etc/ntp.conf, /etc/updatedb.conf, /etc/dhcpcd.conf, /e... scala> scala> val fileGroups = files.map(_.getPath).groupBy(getFileExtension) fileGroups: scala.collection.immutable.Map[String,Array[String]] = Map("" -> Array(/etc/motd, /etc/gemrc, /etc/environment, /etc/gshadow, /etc/shadow-, /etc/fstab, /etc/rpc, /etc/passwd, /etc/gnome-vfs-mime-magic, /etc/passwd-, /etc/sudoers, /etc/anacrontab, /etc/os-release, /etc/adjtime, /etc/netconfig, /etc/inputrc, /etc/timezone, /etc/shadow, /etc/lsb-release, /etc/shells, /etc/papersize, /etc/drirc, /etc/hostname, /etc/exports, /etc/machine-id, /etc/group-, /etc/nanorc, /etc/hosts, /etc/group, /etc/mtab, /etc/securetty, /etc/services, /etc/protocols, /etc/gshadow-, /etc/localtime, /etc/issue, /etc/ethertypes, /etc/manjaro-release, /etc/yaourtrc, /etc/profile, /etc/printcap, /etc/crypttab), backup -> Array(/etc/pacman-mirrors.conf.20170402.backup), bash_logout -> Array(/etc/bash.bash... scala> scala> val fileGroups = files map(_.getPath) groupBy getFileExtension fileGroups: scala.collection.immutable.Map[String,Array[String]] = Map("" -> Array(/etc/motd, /etc/gemrc, /etc/environment, /etc/gshadow, /etc/shadow-, /etc/fstab, /etc/rpc, /etc/passwd, /etc/gnome-vfs-mime-magic, /etc/passwd-, /etc/sudoers, /etc/anacrontab, /etc/os-release, /etc/adjtime, /etc/netconfig, /etc/inputrc, /etc/timezone, /etc/shadow, /etc/lsb-release, /etc/shells, /etc/papersize, /etc/drirc, /etc/hostname, /etc/exports, /etc/machine-id, /etc/group-, /etc/nanorc, /etc/hosts, /etc/group, /etc/mtab, /etc/securetty, /etc/services, /etc/protocols, /etc/gshadow-, /etc/localtime, /etc/issue, /etc/ethertypes, /etc/manjaro-release, /etc/yaourtrc, /etc/profile, /etc/printcap, /etc/crypttab), backup -> Array(/etc/pacman-mirrors.conf.20170402.backup), bash_logout -> Array(/etc/bash.bash... scala> /// Show all file extensions scala> fileGroups.keys.foreach(println) backup bash_logout local pacnew lock conf cache key shutdown updated cfg deny bashrc types rc gen defs scala> fileGroups.get("conf") res90: Option[Array[String]] = Some([Ljava.lang.String;@610b9cb3) scala> fileGroups.get("conf").get res91: Array[String] = Array(/etc/sensors3.conf, /etc/pacman.conf, /etc/cpufreq-bench.conf, /etc/makepkg.conf, /etc/ld.so.conf, /etc/host.conf, /etc/healthd.conf, /etc/ts.conf, /etc/resolvconf.conf, /etc/logrotate.conf, /etc/locale.conf, /etc/request-key.conf, /etc/nscd.conf, /etc/dnsmasq.conf, /etc/nsswitch.conf, /etc/ntp.conf, /etc/updatedb.conf, /etc/dhcpcd.conf, /etc/krb5.conf, /etc/openswap.conf, /etc/vconsole.conf, /etc/mkinitcpio.conf, /etc/man_db.conf, /etc/mke2fs.conf, /etc/fuse.conf, /etc/asound.conf, /etc/mdadm.conf, /etc/pamac.conf, /etc/nfs.conf, /etc/nfsmount.conf, /etc/resolv.conf, /etc/gai.conf, /etc/pacman-mirrors.conf, /etc/rsyncd.conf) scala> fileGroups.get("conf").get.take(10).foreach(println) /etc/sensors3.conf /etc/pacman.conf /etc/cpufreq-bench.conf /etc/makepkg.conf /etc/ld.so.conf /etc/host.conf /etc/healthd.conf /etc/ts.conf /etc/resolvconf.conf /etc/logrotate.conf scala> fileGroups.get("cfg").get.take(10).foreach(println) /etc/vdpau_wrapper.cfg /etc/rc_maps.cfg /// Show all files without extension scala> fileGroups.get("").get.take(10).foreach(println) /etc/motd /etc/gemrc /etc/environment /etc/gshadow /etc/shadow- /etc/fstab /etc/rpc /etc/passwd /etc/gnome-vfs-mime-magic /etc/passwd- // Get the number of files of each extension cala> val fileCounts = fileGroups.mapValues(n => n.length) fileCounts: scala.collection.immutable.Map[String,Int] = Map("" -> 42, backup -> 1, bash_logout -> 1, local -> 1, pacnew -> 1, lock -> 1, conf -> 34, cache -> 1, key -> 1, shutdown -> 1, updated -> 1, cfg -> 2, deny -> 1, bashrc -> 1, types -> 1, rc -> 2, gen -> 1, defs -> 1) // Count the number of *.cfg scala> fileCounts.get("cfg").get res102: Int = 2 // Count the number of files without extension scala> fileCounts.get("") res99: Option[Int] = Some(42) scala> fileCounts.get("conf") res100: Option[Int] = Some(34) scala> fileCounts.get("conf").get res101: Int = 34 // Show how many files are of each extension. // // - 2 files with *.cfg extension and 42 without extension. // scala> fileCounts.foreach(println) (,42) (backup,1) (bash_logout,1) (local,1) (pacnew,1) (lock,1) (conf,34) (cache,1) (key,1) (shutdown,1) (updated,1) (cfg,2) (deny,1) (bashrc,1) (types,1) (rc,2) (gen,1) (defs,1)
Foreach - Apply a function that performs side-effect to each element.
scala> val arr = Array(3, 2, -4, 4, 5, -6) arr: Array[Int] = Array(3, 2, -4, 4, 5, -6) scala> arr.foreach(println) 3 2 -4 4 5 -6 scala> arr foreach println 3 2 -4 4 5 -6 scala> arr.foreach(x => println("x = " + x)) x = 3 x = 2 x = -4 x = 4 x = 5 x = -6 scala> arr foreach (x => println("x = " + x)) x = 3 x = 2 x = -4 x = 4 x = 5 x = -6 // More practical example: // scala> var files = (new java.io.File("/etc/")).listFiles() files: Array[java.io.File] = Array(/etc/systemd, /etc/motd, /etc/gemrc, /etc/adobe, /etc/ld.so.cache, /etc/environment, /etc/libreoffice, /etc/rc_keymaps, /etc/sensors3.conf, /etc/gshadow, /etc/acpi, /etc/pkcs11, /etc/modules-load.d, ... ) // Get the number of files //-------------------------------- scala> files.length res80: Int = 188 scala> files.size res81: Int = 188 scala> files.head res82: java.io.File = /etc/systemd scala> files.last res83: java.io.File = /etc/rsyncd.conf scala> val f = files.head f: java.io.File = /etc/systemd scala> f. // Type tab to show class tabs canExecute getAbsoluteFile getTotalSpace list setWritable canRead getAbsolutePath getUsableSpace listFiles toPath canWrite getCanonicalFile hashCode mkdir toString compareTo getCanonicalPath isAbsolute mkdirs toURI createNewFile getFreeSpace isDirectory renameTo toURL delete getName isFile setExecutable deleteOnExit getParent isHidden setLastModified equals getParentFile lastModified setReadOnly exists getPath length setReadable scala> files.head.getName res84: String = systemd scala> files.head.getPath res86: String = /etc/systemd scala> files.head.toURL res87: java.net.URL = file:/etc/systemd/ scala> files.head.toURI res88: java.net.URI = file:/etc/systemd/ scala> files.head.isFile res89: Boolean = false scala> files.head.isDirectory res90: Boolean = true // Filter all file objects that are directory and take 10 directories. scala> files.filter(_.isDirectory).take(10) res94: Array[java.io.File] = Array(/etc/systemd, /etc/adobe, /etc/libreoffice, /etc/rc_keymaps, /etc/acpi, /etc/pkcs11, /etc/modules-load.d, /etc/gufw, /etc/security, /etc/tmpfiles.d) // Get directories and print 15. // scala> files.filter(_.isDirectory).take(15).foreach(println) /etc/systemd /etc/adobe /etc/libreoffice /etc/rc_keymaps /etc/acpi /etc/pkcs11 /etc/modules-load.d /etc/gufw /etc/security /etc/tmpfiles.d /etc/ppp /etc/iptables /etc/pulse /etc/xinetd.d /etc/ca-certificates // Filter 5 files scala> files.filter(_.isFile).take(5) res96: Array[java.io.File] = Array(/etc/motd, /etc/gemrc, /etc/ld.so.cache, /etc/environment, /etc/sensors3.conf) scala> files.filter(_.isFile).take(15).foreach(println) /etc/motd /etc/gemrc /etc/ld.so.cache /etc/environment /etc/sensors3.conf /etc/gshadow /etc/cron.deny /etc/shadow- /etc/vdpau_wrapper.cfg /etc/pacman.conf /etc/cpufreq-bench.conf /etc/makepkg.conf /etc/ld.so.conf /etc/fstab /etc/host.conf scala> :paste // Entering paste mode (ctrl-D to finish) files .filter(_.isFile) .take(15) .foreach(println) // Exiting paste mode, now interpreting. /etc/motd /etc/gemrc /etc/ld.so.cache /etc/environment /etc/sensors3.conf /etc/gshadow /etc/cron.deny /etc/shadow- /etc/vdpau_wrapper.cfg /etc/pacman.conf /etc/cpufreq-bench.conf /etc/makepkg.conf /etc/ld.so.conf /etc/fstab /etc/host.conf scala> files filter (_.isFile) take 15 foreach println /etc/motd /etc/gemrc /etc/ld.so.cache /etc/environment /etc/sensors3.conf /etc/gshadow /etc/cron.deny /etc/shadow- /etc/vdpau_wrapper.cfg /etc/pacman.conf /etc/cpufreq-bench.conf /etc/makepkg.conf /etc/ld.so.conf /etc/fstab /etc/host.conf
1.7.3.2 Mutable List
scala> import collection.mutable.ListBuffer import collection.mutable.ListBuffer scala> val xs = ListBuffer[Double]() xs: scala.collection.mutable.ListBuffer[Double] = ListBuffer() scala> (1 to 10).foreach(i => xs.append(i.toDouble * 2.5 - 4.0)) scala> xs res42: scala.collection.mutable.ListBuffer[Double] = ListBuffer(-1.5, 1.0, 3.5, 6.0, 8.5, 11.0, 13.5, 16.0, 18.5, 21.0) scala>
1.7.3.3 Mutable Maps collection map hash
scala> import scala.collection.mutable.Map import scala.collection.mutable.Map scala> val hmap1 = Map[Int, String]() hmap1: scala.collection.mutable.Map[Int,String] = Map() scala> hmap1 += (1 -> "Netherlands") res49: hmap1.type = Map(1 -> Netherlands) scala> hmap1 += (2 -> "Mexico") res50: hmap1.type = Map(2 -> Mexico, 1 -> Netherlands) scala> hmap1 += (3 -> "Italy") res51: hmap1.type = Map(2 -> Mexico, 1 -> Netherlands, 3 -> Italy) scala> hmap1 += (10 -> "Japan") res52: hmap1.type = Map(2 -> Mexico, 10 -> Japan, 1 -> Netherlands, 3 -> Italy) scala> scala> hmap1 res53: scala.collection.mutable.Map[Int,String] = Map(2 -> Mexico, 10 -> Japan, 1 -> Netherlands, 3 -> Italy) scala> hmap1(3) res54: String = Italy scala> hmap1(10) res55: String = Japan scala> hmap1(100) java.util.NoSuchElementException: key not found: 100 at scala.collection.MapLike$class.default(MapLike.scala:228) at scala.collection.AbstractMap.default(Map.scala:59) at scala.collection.mutable.HashMap.apply(HashMap.scala:65) ... 32 elided scala>
1.7.4 Seq Trait/Interface
Seq is trait or interface for collections with iterable interface and defined order of elements such as Array, List, and Vector which implement Seq trait.
Example 1: Generalizing functions that operates over Seq collections.
The function printElements only works for Arrays of ints.
@ def printElements(xs: Array[Int]) = xs foreach println defined function printElements @ @ printElements(Array(1, 2, 3, 4, 5)) 1 2 3 4 5 @ printElements(List(1, 2, 3, 4, 5)) cmd3.sc:1: type mismatch; found : List[Int] required: Array[Int] val res3 = printElements(List(1, 2, 3, 4, 5)) ^ Compilation Failed @ @ printElements(Vector(1, 2, 3, 4, 5)) cmd3.sc:1: type mismatch; found : scala.collection.immutable.Vector[Int] required: Array[Int] val res3 = printElements(Vector(1, 2, 3, 4, 5)) ^ Compilation Failed @
This function can be refactored in order to work with all those collections by replacing Array with Seq.
@ def printElements(xs: Seq[Int]) = xs foreach println defined function printElements @ printElements(Array(1, 2, 3, 4, 5)) 1 2 3 4 5 @ printElements(List(1, 2, 3, 4, 5)) 1 2 3 4 5 @ printElements(Vector(1, 2, 3, 4, 5)) 1 2 3 4 5
Example 2: Some Seq operations.
@ val seq1: Seq[Int] = Array(1, 2, 3, 4) seq1: Seq[Int] = WrappedArray(1, 2, 3, 4) @ val seq2: Seq[Int] = Vector(1, 2, 3, 4) seq2: Seq[Int] = Vector(1, 2, 3, 4) @ val seq3: Seq[Int] = List(1, 3, 4) seq3: Seq[Int] = List(1, 3, 4) @ seq1.sum res10: Int = 10 @ seq2.sum res11: Int = 10 @ seq3.sum res12: Int = 8 @ List(seq1, seq2, seq3) map (_.sum) res13: List[Int] = List(10, 10, 8) @ @ List(seq1, seq2, seq3) map (_.product) res14: List[Int] = List(24, 24, 12) @ @ def sum(s: Seq[Int]) = s.sum defined function sum @ @ sum(seq1) res16: Int = 10 @ sum(seq2) res17: Int = 10 @ sum(seq3) res18: Int = 8 @ @ sum(seq4) res20: Int = 0 @ @ val seq4 = scala.collection.mutable.ListBuffer[Int]() seq4: collection.mutable.ListBuffer[Int] = ListBuffer() @ @ seq4.sum res21: Int = 0 @ seq4.append(1) @ seq4.append(2) @ seq4.append(3) @ seq4 res27: collection.mutable.ListBuffer[Int] = ListBuffer(1, 2, 3) @ @ seq4.sum res25: Int = 6 @ @ sum(seq4) res28: Int = 6 @ @ def getSecond[A](xs: Seq[A]) = xs(1) defined function getSecond @ getSecond @ getSecond(seq1) res30: Int = 2 @ getSecond(seq2) res31: Int = 2 @ getSecond(seq3) res32: Int = 3 @ getSecond(seq4) res33: Int = 2 @
References:
- Scala Standard Library 2.12.0 - scala.collection.Seq. Available at http://www.scala-lang.org/api/2.12.0/scala/collection/Seq.html
1.8 Case classes and pattern matching
Scala case classes are similar to Haskell algebraic data types and supports pattern matching. It is useful to represent abstract syntax trees and build interpreters and pasers.
Example 1:
- Sealed class means that it is not possible to define any other sublclass of class Shape.
sealed abstract class Shape case class Square (side: Double) extends Shape case class Rectangle(height: Double, width: Double) extends Shape case class Circle (radius: Double) extends Shape case class Triangle (a: Double, b: Double, c: Double) extends Shape def computeArea(shape: Shape) = shape match { case Square(x) => x * x case Rectangle(w, h) => w * h case Circle(r) => Math.PI * r * r case _ => error("Error: Not implemented. See Heron's formula.") } def computePerimiter(shape: Shape) = shape match { case Square(x) => 4 * x case Rectangle(w, h) => 2 * (w + h) case Circle(r) => 2 * Math.PI * r case Triangle(a, b, c) => a + b + c } def classify(shape: Shape) = shape match { case Square(_) => "square" case Rectangle(_,_) => "rectangle" case Circle(_) => "circle" case Triangle(_,_,_) => "triangle" } scala> val s = Square(10.0) s: Square = Square(10.0) scala> val r = Rectangle(5.0, 10.0) r: Rectangle = Rectangle(5.0,10.0) scala> val c = Circle(3.0) c: Circle = Circle(3.0) scala> val t = Triangle(2.0, 4.0, 5.0) t: Triangle = Triangle(2.0,4.0,5.0) scala> scala> computeArea(s) res27: Double = 100.0 scala> computeArea(r) res28: Double = 50.0 scala> computeArea(t) java.lang.RuntimeException: Error: Not implemented. See heron formula. at scala.sys.package$.error(package.scala:27) at scala.Predef$.error(Predef.scala:144) at .computeArea(<console>:25) ... 32 elided scala> scala> List(s, r, t) res30: List[Product with Serializable with Shape] = List(Square(10.0), Rectangle(5.0,10.0), Triangle(2.0,4.0,5.0)) scala> List(s, r, t).foreach(println) Square(10.0) Rectangle(5.0,10.0) Triangle(2.0,4.0,5.0) scala> List(s, r, t).map(classify) res32: List[String] = List(square, rectangle, triangle) scala> List(s, r, t).map(computePerimiter) res33: List[Double] = List(40.0, 30.0, 11.0)
Example 2: Abstract syntax tree.
sealed abstract class Expr case class Val(x: Int) extends Expr case class Add(r: Expr, s: Expr) extends Expr case class Sub(r: Expr, s: Expr) extends Expr case class Mul(r: Expr, s: Expr) extends Expr // It is possible to define multiple interpreters for this AST. // This interpreter evaluates the AST def evalExpr(expr: Expr): Int = expr match { case Val(n) => n case Add(r, s) => computeExpr(r) + computeExpr(s) case Sub(r, s) => computeExpr(r) - computeExpr(s) case Mul(r, s) => computeExpr(r) * computeExpr(s) } // This interpreter show the expresion def showExpr(expr: Expr): String = expr match { case Val(n) => n.toString case Add(Val(x), Val(y)) => x + " + " + y case Sub(Val(x), Val(y)) => x + " - " + y case Mul(Val(x), Val(y)) => x + " * " + y case Add(Val(x), s) => x + " + (" + showExpr(s) + ")" case Sub(Val(x), s) => x + " - (" + showExpr(s) + ")" case Mul(Val(x), s) => x + " * (" + showExpr(s) + ")" case Add(r, Val(y)) => "(" + showExpr(r) + ") + " + y case Sub(r, Val(y)) => "(" + showExpr(r) + ") - " + y case Mul(r, Val(y)) => "(" + showExpr(r) + ") * " + y case Add(r, s) => "(" + showExpr(r) + ") + (" + showExpr(s) + ")" case Sub(r, s) => "(" + showExpr(r) + ") - (" + showExpr(s) + ")" case Mul(r, s) => "(" + showExpr(r) + ") * (" + showExpr(s) + ")" } // This interpreter shows the expresion and its value def showEval(expr: Expr) = { println(showExpr(expr) + " = " + evalExpr(expr)) } scala> val e1 = Val(10) e1: Val = Val(10) scala> val e2 = Add(Val(10), Val(5)) e2: Add = Add(Val(10),Val(5)) scala> val e3 = Mul(Val(3), e2) e3: Mul = Mul(Val(3),Add(Val(10),Val(5))) scala> val e4 = Sub(e3, e1) e4: Sub = Sub(Mul(Val(3),Add(Val(10),Val(5))),Val(10)) scala> evalExpr(e1) res56: Int = 10 scala> evalExpr(e2) res57: Int = 15 scala> evalExpr(e3) res58: Int = 45 scala> evalExpr(e4) res59: Int = 35 scala> showExpr(e1) res60: String = 10 scala> showExpr(e2) res61: String = 10 + 5 scala> showExpr(e3) res62: String = 3 * (10 + 5) scala> showExpr(e4) res63: String = (3 * (10 + 5)) - 10 scala> List(e1, e2, e3, e4).foreach(println) Val(10) Add(Val(10),Val(5)) Mul(Val(3),Add(Val(10),Val(5))) Sub(Mul(Val(3),Add(Val(10),Val(5))),Val(10)) scala> List(e1, e2, e3, e4).foreach(showEval) 10 = 10 10 + 5 = 15 3 * (10 + 5) = 45 (3 * (10 + 5)) - 10 = 35
1.9 Pattern Matching
1.9.1 Pattern matching numbers
Example 1 - Show month name.
def getMonthName(month: Int) = month match { case 1 => "January" case 2 => "February" case 3 => "March" case 4 => "April" case 5 => "May" case 6 => "June" case 7 => "July" case 8 => "August" case 9 => "September" case 10 => "October" case 11 => "November" case 12 => "December" case _ => error("Error: Invalid month") } scala> getMonthName(1) res75: String = January scala> getMonthName(9) res76: String = September scala> getMonthName(0) java.lang.RuntimeException: Error: Invalid month at scala.sys.package$.error(package.scala:27) at scala.Predef$.error(Predef.scala:144) at .getMonthName(<console>:26) ... 32 elided def getMonthName2(month: Int) = month match { case 1 => Some("January") case 2 => Some("February") case 3 => Some("March") case 4 => Some("April") case 5 => Some("May") case 6 => Some("June") case 7 => Some("July") case 8 => Some("August") case 9 => Some("September") case 10 => Some("October") case 11 => Some("November") case 12 => Some("December") case _ => None } scala> getMonthName2(1) res78: Option[String] = Some(January) scala> getMonthName2(12) res79: Option[String] = Some(December) scala> getMonthName2(0) res80: Option[String] = None scala> getMonthName2(-10) res81: Option[String] = None
1.9.2 Pattern matching with strings.
Example 2 - Pattern matching with strings.
def showCurrencyName(cur: String) = cur match { case "USD" => "United States Dollar" case "CAD" => "Canadian Dollar" case "EUR" => "Euro" case "AUD" => "Australian Dollar" case "GBP" => "Great Britain Pound" case "JPY" => "Japanese Yen" case "CNY" => "Chinese Yuan / Reminbi" case "HKD" => "Hong Kong Dollar" case "BRL" => "Brazilian Real" case "MXN" => "Mexican Peso" case "CHF" => "Switzerland Franc" case "XBT" => "Bitcoin" case _ => error("Error: I don't the name of this currency. Please teach me it.") } scala> showCurrencyName("CNY") res102: String = Chinese Yuan / Reminbi scala> showCurrencyName("JPY") res103: String = Japanese Yen scala> showCurrencyName("HKD") res104: String = Hong Kong Dollar scala> showCurrencyName("JPYx") java.lang.RuntimeException: Error: I don't the name of this currency. Please teach me it. at scala.sys.package$.error(package.scala:27) at scala.Predef$.error(Predef.scala:144) at .showCurrencyName(<console>:26) ... 32 elided scala> List("CNY", "HKD", "AUD", "BRL").map(showCurrencyName).foreach(println) Chinese Yuan / Reminbi Hong Kong Dollar Australian Dollar Brazilian Real scala> showCurrencyName("XBT") res109: String = Bitcoin
1.9.3 Pattern with number and case-classes.
Example 3 - Pattern with number and case-classes.
abstract sealed class Month case object Jan extends Month case object Feb extends Month case object Mar extends Month case object Apr extends Month case object May extends Month case object Jun extends Month case object Jul extends Month case object Aug extends Month case object Sep extends Month case object Oct extends Month case object Nov extends Month case object Dec extends Month def getMonthName(month: Month) = month match { case Jan => "January" case Feb => "February" case Mar => "March" case Apr => "April" case May => "May" case Jun => "June" case Jul => "July" case Aug => "August" case Sep => "September" case Oct => "October" case Nov => "November" case Dec => "December" } def numberToMonth(n: Int) = n match { case 1 => Jan case 2 => Feb case 3 => Mar case 4 => Apr case 5 => May case 6 => Jun case 7 => Jul case 8 => Aug case 9 => Sep case 10 => Oct case 11 => Nov case 12 => Dec case _ => error("Error: Invalid month number.") } def numberToMonth2(n: Int) = n match { case 1 => Some(Jan) case 2 => Some(Feb) case 3 => Some(Mar) case 4 => Some(Apr) case 5 => Some(May) case 6 => Some(Jun) case 7 => Some(Jul) case 8 => Some(Aug) case 9 => Some(Sep) case 10 => Some(Oct) case 11 => Some(Nov) case 12 => Some(Dec) case _ => None } scala> getMonthName(Jan) res85: String = January scala> getMonthName(Feb) res86: String = February scala> getMonthName(Aug) res87: String = August scala> getMonthName(Dec) res88: String = December scala> numberToMonth(1) res93: Product with Serializable with Month = Jan scala> numberToMonth(9) res94: Product with Serializable with Month = Sep scala> numberToMonth(-1) java.lang.RuntimeException: Error: Invalid month number. at scala.sys.package$.error(package.scala:27) at scala.Predef$.error(Predef.scala:144) at .numberToMonth(<console>:39) ... 32 elided scala> numberToMonth2(1) res97: Option[Product with Serializable with Month] = Some(Jan) scala> numberToMonth2(2) res98: Option[Product with Serializable with Month] = Some(Feb) scala> numberToMonth2(12) res99: Option[Product with Serializable with Month] = Some(Dec) scala> numberToMonth2(-1) res100: Option[Product with Serializable with Month] = None scala> numberToMonth2(0) res101: Option[Product with Serializable with Month] = None
1.9.4 Pattern matching with conditional.
Example 4 - Pattern matching with conditional.
def classifyNumber(n: Int) = n match { case x if x < 0 => println("Negative number") case 0 => println("zero") case x if x > 0 && x % 2 == 0 => println("Positive even number") case _ => println("Positive odd number") } scala> classifyNumber(-2) Negative number scala> classifyNumber(-10) Negative number scala> classifyNumber(0) zero scala> classifyNumber(2) Positive even number scala> classifyNumber(21) Positive odd number scala> classifyNumber(210) Positive even number scala> classifyNumber(1213) Positive odd number
1.9.5 Recursive functions
Example 5 - Recursive functions.
def factorial(n: Int): Int = n match { case a if a < 0 => error("Error: Invalid input.") case 0 | 1 => 1 case k => k * factorial(k - 1) } scala> factorial(4) res126: Int = 24 scala> factorial(5) res127: Int = 120 scala> factorial(-5) java.lang.RuntimeException: Error: Invalid input. at scala.sys.package$.error(package.scala:27) at scala.Predef$.error(Predef.scala:144) at .factorial(<console>:14) ... 32 elided scala> factorial(0) res129: Int = 1 scala> factorial(1) res130: Int = 1
Example 6 - Recursive list functions.
scala> 1::List() res118: List[Int] = List(1) scala> 2::1::List() res119: List[Int] = List(2, 1) scala> 4::2::1::List() res120: List[Int] = List(4, 2, 1) def sumList(list: List[Int]): Int = list match { case List() => 0 case hd::tl => hd + sumList(tl) } scala> sumList(List(1, 2, 3, 4, 5)) res121: Int = 15 scala> def foreach[A](fn: A => Unit)(list: List[A]): Unit = list match { case List() => () // Do nothing. case hd::tl => fn(hd) ; foreach(fn)(tl) } scala> def foreach[A](fn: A => Unit)(list: List[A]): Unit = list match { | case List() => () // Do nothing. | case hd::tl => fn(hd) ; foreach(fn)(tl) | } foreach: [A](fn: A => Unit)(list: List[A])Unit scala> foreach(println)(List(1, 2, 3, 4)) 1 2 3 4 scala> val printAll = foreach(println)(_) printAll: List[Any] => Unit = <function1> scala> printAll(List("Hello", "World", "Scala")) Hello World Scala scala> printAll(List(1, 2, 3, 4, 5)) 1 2 3 4 5
1.9.6 Destructuring
1.9.6.1 Destructuring tuples
scala> val (a, b) = (10, 20) a: Int = 10 b: Int = 20 scala> a + b res0: Int = 30 scala> val tpl = (10, "hello", 'x') tpl: (Int, String, Char) = (10,hello,x) scala> val (x, y, z) = tpl x: Int = 10 y: String = hello z: Char = x scala> x res1: Int = 10 scala> y res2: String = hello scala> z res3: Char = x
1.9.6.2 Destructuring List, Arrays and other collections
scala> val List(a, b, c) = List(1, 2, 3) a: Int = 1 b: Int = 2 c: Int = 3 scala> val List(a, b, c) = List(1, 2, 3, 4, 5, 6) scala.MatchError: List(1, 2, 3, 4, 5, 6) (of class scala.collection.immutable.$colon$colon) ... 29 elided scala> val Array(x, y, z) = Array(1, 2, 3) x: Int = 1 y: Int = 2 z: Int = 3 scala> x + y + z res7: Int = 6
1.9.6.3 Destructuring case classes
case class Product( id: Int, name: String, price: BigDecimal, quantity: Int ) val inventory = List( Product(100, "Sugar", 1.24, 100), Product(201, "Carton of Milk", 2.25, 200), Product(300, "Carton of fresh orange juice", 4.5, 300), ) def showProduct(product: Product) = { val Product(id, name, price, qnt) = product println(s""" name = ${name} id = ${id} price = ${price} quantity = ${qnt} """.trim() + "\n" ) } // Alternative way without destructuring def showProduct2(product: Product) = { println(s""" name = ${product.name} id = ${product.id} price = ${prodcut.price} quantity = ${product.quantity} """.trim() + "\n" ) } def getInventoryValue(inventory: List[Product]) = { inventory.foldLeft(0: BigDecimal){ (acc, product) => val Product(_, _, price, qnt) = product acc + price * qnt } }
Running:
scala> inventory foreach showProduct name = Sugar id = 100 price = 1.24 quantity = 100 name = Carton of Milk id = 201 price = 2.25 quantity = 200 name = Carton of fresh orange juice id = 300 price = 4.5 quantity = 300 scala> getInventoryValue(inventory) res11: BigDecimal = 1924.00
1.10 Option type
1.10.1 Overview
Scala's Option type is similar to Haskell's Maybe and to OCaml's Option type which allows to deal with null values, avoid nested null checkings and also lots of null exceptions or null pointer exception.
1.10.2 Basic operations
scala> Some(100) res0: Some[Int] = Some(100) scala> None res1: None.type = None scala> Option(100) res2: Option[Int] = Some(100) scala> Option(null) res3: Option[Null] = None scala> Option("opt") res4: Option[String] = Some(opt) scala> Option(null) : Option[String] res5: Option[String] = None scala> None : Option[String] res6: Option[String] = None
1.10.3 Pattern matching
Pattern matching an Option value
def testPatternMatching(opt: Option[String]) = opt match { case Some(x) => println("Received: " + x) case None => println("Received: None") } scala> def testPatternMatching(opt: Option[String]) = opt match { | case Some(x) => println("Received: " + x) | case None => println("Received: None") | } testPatternMatching: (opt: Option[String])Unit scala> testPatternMatching(Some("avoiding null exceptions")) Received: avoiding null exceptions scala> testPatternMatching(None) Received: None scala> testPatternMatching(Option("avoiding null exceptions")) Received: avoiding null exceptions scala> testPatternMatching(Option(null)) Received: None
Pattern matching an option tuple:
def safeSum(optA: Option[Double], optB: Option[Double]) = (optA, optB) match { case (Some(a), Some(b)) => Some(a + b) case _ => None } scala> safeSum(Some(10), Some(20)) res16: Option[Double] = Some(30.0) scala> safeSum(None, Some(20)) res17: Option[Double] = None scala> safeSum(Some(10), None) res18: Option[Double] = None scala> safeSum(None, None) res19: Option[Double] = None
1.10.4 Option type as a collection
The Scala's option is similar to Scala's collections like List, Array and Map with lots of useful higher order methods that accepts functions as argument.
Get - Get the value of wrapped by the option type, but throws an exception if it is None.
scala> val x1 = Some("Hello") x1: Some[String] = Some(Hello) scala> val x2 = Option("World") x2: Option[String] = Some(World) scala> val x3 = None x3: None.type = None scala> val x4: Option[String] = Option(null) x4: Option[String] = None scala> x1.get res50: String = Hello scala> x2.get res51: String = World scala> x3.get java.util.NoSuchElementException: None.get at scala.None$.get(Option.scala:349) ... 29 elided scala> x4.get java.util.NoSuchElementException: None.get at scala.None$.get(Option.scala:349) at scala.None$.get(Option.scala:347) ... 29 elided scala>
GetOrElse
scala> None.getOrElse _ res111: (=> Nothing) => Nothing scala> Some(20).getOrElse(4) res112: Int = 20 scala> (None: Option[Int]).getOrElse(4) res113: Int = 4 scala> Some("hello").getOrElse("world") res114: String = hello scala> Option(null).getOrElse("world") res115: String = world scala> None.getOrElse("world") res116: String = world
Map: - Apply a function to the option value wrapped by the option type.
scala> val o1 = Some(10) o1: Some[Int] = Some(10) // Test with Some value // scala> val o2: Option[Int] = None o2: Option[Int] = None scala> o1.map(x => x * 3) res24: Option[Int] = Some(30) scala> o1.map(x => x * 3).map(x => x + 10) res26: Option[Int] = Some(40) // Test with None values // scala> o2 res27: Option[Int] = None scala> o2 map (x => x * 3) res28: Option[Int] = None scala> o2 map (x => x * 3) map (x => x + 10) res29: Option[Int] = None
Flatmap - It is similar to Haskell bind operator (>>=). But in Scala it is method, not a function as in Haskell.
scala> Option(10) map (x => if (x > 5) Some(10) else None) res32: Option[Option[Int]] = Some(Some(10)) scala> Option(3) map (x => if (x > 5) Some(10) else None) res33: Option[Option[Int]] = Some(None) scala> (None: Option[Int]) map (x => if (x > 5) Some(10) else None) res35: Option[Option[Int]] = None scala> Some(10) flatMap (x => if (x > 5) Some(10) else None) res36: Option[Int] = Some(10) scala> Some(2) flatMap (x => if (x > 5) Some(10) else None) res37: Option[Int] = None scala> (None: Option[Int]) flatMap (x => if (x > 5) Some(10) else None) res38: Option[Int] = None
Flatten
scala> Some(Some(10)).flatten res143: Option[Int] = Some(10) scala> Some(None).flatten res144: Option[Nothing] = None scala> None.flatten res145: Option[Nothing] = None scala> (Some(None): Option[Option[Int]]).flatten res146: Option[Int] = None scala> (None: Option[Option[Int]]).flatten res147: Option[Int] = None
Foreach - Apply a function that performs some action to the value wrapped by the option type.
scala> Some("Scala Option") foreach {s => println("Message = " + s)} Message = Scala Option scala> None foreach {s => println("Message = " + s)} scala> Option("Scala Option") foreach {s => println("Message = " + s)} Message = Scala Option scala> Option(null) foreach {s => println("Message = " + s)} scala>
Find
scala> Some(100).find(a => a < 10) res122: Option[Int] = None scala> Some(4).find(a => a < 10) res123: Option[Int] = Some(4) scala> Some(4) find (a => a < 10) res126: Option[Int] = Some(4) scala> (None: Option[Int]).find(a => a < 10) res125: Option[Int] = None
Filter
scala> Some(3).filter (a => a > 10) res131: Option[Int] = None scala> Some(100).filter (a => a > 10) res132: Option[Int] = Some(100) scala> (None: Option[Int]).filter (a => a > 10) res133: Option[Int] = None scala> Some(3).filter (_ > 10) res137: Option[Int] = None scala> Some(100).filter (_ > 10) res138: Option[Int] = Some(100) scala> (None: Option[Int]).filter (_ > 10) res139: Option[Int] = None
Conversions
scala> Some("Scala").toArray res160: Array[String] = Array(Scala) scala> (None: Option[String]).toArray res161: Array[String] = Array() scala> Option("Scala").toArray res162: Array[String] = Array(Scala) scala> (Option(null): Option[String]).toArray res163: Array[String] = Array() scala> Option("Scala").toList res164: List[String] = List(Scala) scala> (None: Option[String]).toList res165: List[String] = List() scala> Some("Scala").toList res166: List[String] = List(Scala) scala>
1.10.5 For-loop
For-loop: Option's for-loop is similar to Array's or list for-loop.
scala> for(s <- List(1, 2, 3, 4)) println("x = " + s) x = 1 x = 2 x = 3 x = 4 scala> for(s <- Some("scala")) println("Message = " + s) Message = scala scala> for(s <- None) println("Message = " + s) scala>
1.10.6 For-comprehensions
Scala's for comprehension are very similar to Haskell's do-notation for Monad.
Example 1:
Scala:
def safeSum(sa: Option[Int], sb: Option[Int]) = { for { a <- sa b <- sb } yield a + b } scala> safeSum(Some(10), Some(20)) res170: Option[Int] = Some(30) scala> safeSum(Some(10), None) res171: Option[Int] = None scala> safeSum(None, None) res172: Option[Int] = None /// -- Expanding to for-notation ------- def safeSum2(sa: Option[Int], sb: Option[Int]) = { sa.flatMap { a => sb.map { b => a + b} } } @ safeSum2 _ res22: (Option[Int], Option[Int]) => Option[Int] @ safeSum2(Some(10), Some(2)) res23: Option[Int] = Some(12) @ safeSum2(Some(10), None) res24: Option[Int] = None @ safeSum2(None, None) res25: Option[Int] = None
Haskell:
:{ safeSum :: Maybe Int -> Maybe Int -> Maybe Int safeSum sa sb = do a <- sa b <- sb return ( a + b) :} > safeSum (Just 10) (Just 20) Just 30 it :: Maybe Int > safeSum (Just 10) Nothing Nothing it :: Maybe Int > safeSum Nothing Nothing Nothing it :: Maybe Int >
Example 2:
def both[A, B](oa: Option[A], ob: Option[B]) = for { a <- oa b <- ob c = (a, b) } yield c scala> both(Some(10), Some("hello")) res214: Option[(Int, String)] = Some((10,hello)) scala> both(Some(10), None: Option[String]) res215: Option[(Int, String)] = None def both2[A, B](oa: Option[A], ob: Option[B]) = for { a <- oa b <- ob } yield (a, b) scala> both2(Some(10), Some("hello")) res216: Option[(Int, String)] = Some((10,hello)) scala> both2(None: Option[Int], Some("hello")) res217: Option[(Int, String)] = None scala> both2(None: Option[Int], None: Option[String]) res218: Option[(Int, String)] = None
1.10.7 Avoiding null checking and null pointer exceptions
The function getClibpoardText gets a string from clibpoard. It is non-safe and error prone as it is full of null checking and returns a null value. If the developer forgets to check for null at the point of use, it may lead the unexpected null-pointer exceptions at runtime that cannot be spot at compile-time or spot by the type system.
It may get worse if the return value of this function is passed to other functions that dont't check for null values.
- Situation 1: getClibpoardText will null checking and with possibility of returning null value.
import java.awt.Toolkit import java.awt.datatransfer.{Clipboard, DataFlavor, Transferable} def getClipboardText() = { val clip = Toolkit.getDefaultToolkit().getSystemClipboard() if (clip == null) null else { val data = clip.getContents(null) if (data != null && data.isDataFlavorSupported(DataFlavor.getTextPlainUnicodeFlavor())) data.getTransferData(DataFlavor.stringFlavor).asInstanceOf[String] else null } } /// User copies some text and run this function /// scala> val text1 = getClipboardText() text1: String = "A class that implements a mechanism to transfer data using cut/copy/paste operations. FlavorListeners may be registered on an instance of the Clipboard class to be notified about changes to the set of DataFlavors available on this clipboard (see addFlavorListener(java.awt.datatransfer.FlavorListener)). " scala> text1.length res188: Int = 305 /// User copies some image and run this function /// scala> val text2 = getClipboardText() text2: String = null scala> text2.length java.lang.NullPointerException ... 29 elided scala> { val text3 = getClipboardText() ; if (text3 != null) text3.length else null } res190: Any = null def tellSize1(s: String) = println("String size is = " + s.length) /// User copies some text scala> tellSize1(getClipboardText()) String size is = 305 /// User copies some image. The function getClibpoardtext returns null. scala> tellSize1(getClipboardText()) java.lang.NullPointerException at .tellSize1(<console>:21) ... 29 elided def tellSize2(s: String) = { if (s != null) println("String size is = " + s.length) } scala> tellSize2(getClipboardText()) String size is = 94 scala> tellSize2(getClipboardText())
By refactoring the aforementioned code to use option type instead of null values, it leads to a more concise and type-safe code where it is no longer necessary to check for null values and there is no more risk of unexpected null exceptions at run-time.
- Situation 2: getClibpoardText with Option type.
import java.awt.Toolkit import java.awt.datatransfer.{Clipboard, DataFlavor, Transferable} def getClipboardText() = for { clip <- Option(Toolkit.getDefaultToolkit().getSystemClipboard()) data <- Option(clip.getContents(null)) if data.isDataFlavorSupported(DataFlavor.getTextPlainUnicodeFlavor()) text = data.getTransferData(DataFlavor.stringFlavor).asInstanceOf[String] } yield text scala> getClipboardText _ res200: () => Option[String] //---------------------------- // // User copies some text scala> getClipboardText() res201: Option[String] = Some(JGoodies Binding: Property Adapter Example 3 : Data Binding « Swing Components « Java) // User copies an image scala> getClipboardText() res202: Option[String] = None //---------------------------- // // User copies some text scala> getClipboardText() foreach println def error(message: String) = throw new Exception(message) // User copies an image scala> getClipboardText() foreach println //---------------------------- // // User copies some text scala> getClipboardText() map (_.length) res207: Option[Int] = Some(23) scala> getClipboardText().map(_.length).get res211: Int = 68 // User copies an image scala> getClipboardText() map (_.length) res208: Option[Int] = None scala> getClipboardText().map(_.length).get java.util.NoSuchElementException: None.get at scala.None$.get(Option.scala:349) at scala.None$.get(Option.scala:347) ... 29 elided scala> def tellSize1(s: String) = println("String size is = " + s.length) tellSize1: (s: String)Unit scala> getClipboardText() foreach tellSize1 String size is = 101
It can be refactored to more sef-contained code.
def getClipboardText() = { import java.awt.Toolkit import java.awt.datatransfer.{Clipboard, DataFlavor, Transferable} for { clip <- Option(Toolkit.getDefaultToolkit().getSystemClipboard()) data <- Option(clip.getContents(null)) if data.isDataFlavorSupported(DataFlavor.getTextPlainUnicodeFlavor()) text = data.getTransferData(DataFlavor.stringFlavor).asInstanceOf[String] } yield text }
1.11 Exception Handling with try-catch
1.11.1 Overview
An exception is an event that happens during the execution of a programs which disrupts the normal flow of execution or an unexpected runtime error that terminates the execution abnormally. Handling them is necessary to create robust programs resilient that can react to failures, invalid input, network error and so on.
Exception Hierarchy
- Throwable
- Error - System errors or fatal error thrown by JVM that ends the program
execution and should not be handled by the application.
- LinkageError
- VirtualMachineError
- AWTError
- AssertionError
- Cause: Assertion errors happens when the program assumptions
are wrong, for instance,
assert (10 == 0, "The result should be 10")
- Cause: Assertion errors happens when the program assumptions
are wrong, for instance,
- … … … …
- Exception - Errors that can be handled by the program.
- ClassNotFoundException
- SQLException
- IOException
- FileNotFoundException
- MalformedURLException
- SocketException
- EOFException
- InterruptedIOException
- RuntimeException
- ArtithmeticException
- Case: Division by zero.
- NullPointerException
- Cause: Calling a method or accessing a field of a class which the value is set to null.
- IndexOutofBoundsException
- Cause: Accessing out-of-bounds array elements.
- IllegalArgumentException
- Cause: Sending illegal argument to a method like sending a negative number to the method .runWithDelay(time: Int).
- IllegalStateException
- NumberFormatException
- Cause: Attempt to convert a bad-formatted string to some
value. Example:
Integer.parseInt("xyz")
- Cause: Attempt to convert a bad-formatted string to some
value. Example:
- ClassCastException
- Cause: Invalid casting.
- … … … … …
- ArtithmeticException
- Error - System errors or fatal error thrown by JVM that ends the program
execution and should not be handled by the application.
Exception Methods:
- .getMessage() - Get exception Message.
- .getStackTrace()
- .getStackTraceString()
- .printStackTrace()
1.11.2 Throwing Exceptions
scala> throw new IllegalArgumentException("Error: Amount to withdraw cannot excede balance") java.lang.IllegalArgumentException: Error: Amount to withdraw cannot excede balance ... 29 elided scala> throw new Exception("Error: Invalid user input. Fatal kernel failure.") java.lang.Exception: Error: Invalid user input. Fatal kernel failure. ... 29 elided scala> throw new java.lang.ArrayIndexOutOfBoundsException() java.lang.ArrayIndexOutOfBoundsException ... 29 elided scala> throw new NullPointerException() java.lang.NullPointerException ... 29 elided scala> throw new AssertionError("The result should be 10") java.lang.AssertionError: The result should be 10 ... 29 elided scala> throw new Error("Fatal failure. Aborting exectution") java.lang.Error: Fatal failure. Aborting exectution ... 29 elided
1.11.3 Catching exceptions without finally block.
Example 1
def testException1(block: => Unit) = { try block catch { case ex: IllegalArgumentException => println("Error: Illegal exception") case ex: IllegalStateException => println("Error: Illegal state exception") case ex: java.io.FileNotFoundException => println("Error: File not found exception ") case ex: Exception => println("Error: Unknown exception") } } scala> testException1{ println("It is not gonna fail.") } It is not gonna fail. scala> testException1{ new java.io.FileReader("/etc/somefile") } Error: File not found exception scala> testException1{ assert(10 == 0) } java.lang.AssertionError: assertion failed at scala.Predef$.assert(Predef.scala:204) at .$anonfun$res41$1(<console>:13) at .testException1(<console>:12) ... 29 elided
Example 2: Catching exceptions with try block.
def showException(label: String, ex: Exception) = { println(label) println(ex) println(ex.printStackTrace()) } def testException2(block: => Unit) = { try block catch { case ex: java.io.FileNotFoundException => showException("Error: File not found exception", ex) case ex: java.io.IOException => showException("Error: IO Exception", ex) case ex: NullPointerException => showException("Error: Null exception", ex) case ex: IllegalArgumentException => showException("Error: Illegal Argument exception.", ex) case ex: IllegalStateException => showException("Error: Illegal state exception", ex) case ex: Exception => showException("Error: Unknown exception", ex) } finally { println("After 'finally' statement this piece of code always runs.") } } scala> testException2{ println("This code will not throw an exception") } This code will not throw an exception After 'finally' statement this piece of code always runs. scala> testException2{ println(10/ 0) } Error: Unknown exception java.lang.ArithmeticException: / by zero java.lang.ArithmeticException: / by zero at $line86.$read$$iw$$iw$.$anonfun$res30$1(<console>:13) at $line85.$read$$iw$$iw$.testException2(<pastie>:20) at $line86.$read$$iw$$iw$.<init>(<console>:13) at $line86.$read$$iw$$iw$.<clinit>(<console>) at $line86.$eval$.$print$lzycompute(<console>:7) at $line86.$eval$.$print(<console>:6) ... ... () After 'finally' statement this piece of code always runs. scala> testException2{ val x: String = null ; println(x.length) } Error: Null exception java.lang.NullPointerException java.lang.NullPointerException at $line87.$read$$iw$$iw$.$anonfun$res31$1(<console>:14) at $line85.$read$$iw$$iw$.testException2(<pastie>:20) ... ... ... ... () After 'finally' statement this piece of code always runs. scala> testException2{ new java.io.FileReader("/etc/fstabx") } Error: File not found exception java.io.FileNotFoundException: /etc/fstabx (No such file or directory) java.io.FileNotFoundException: /etc/fstabx (No such file or directory) at java.io.FileInputStream.open0(Native Method) at java.io.FileInputStream.open(FileInputStream.java:195) at java.io.FileInputStream.<init>(FileInputStream.java:138) ... ... ... ... () After 'finally' statement this piece of code always runs. // Exception not handled. // scala> testException2{ assert(10 == 5, "Result should be 10") } After 'finally' statement this piece of code always runs. java.lang.AssertionError: assertion failed: Result should be 10 at scala.Predef$.assert(Predef.scala:219) at .$anonfun$res32$1(<console>:13) at .testException2(<pastie>:20) ... 29 elided
Example 3: Catching exception and releasing resources.
def displayFile(file: String) = { var fd: java.io.FileReader = null try { fd = new java.io.FileReader(file) val in = new java.util.Scanner(fd) while(in.hasNextLine()){ println(in.nextLine()) } } catch { case ex: java.io.FileNotFoundException => println("Error: File not found: ") } finally { if (fd != null) fd.close() } } scala> displayFile("/etc/lsb-release") DISTRIB_ID=ManjaroLinux DISTRIB_RELEASE=17.0.2 DISTRIB_CODENAME=Gellivara DISTRIB_DESCRIPTION="Manjaro Linux" scala> displayFile("/etc/lsb-releaseq") Error: File not found:
1.11.4 References
- Chapter 14 - Exception Handling and Text Files - http://grail.cba.csuohio.edu/~matos/notes/cis-265/lecture-notes/03-13slide-Exceptions.pdf
- CSC 102 Lecture Notes Week 5 Shallowness and Deepness Exceptions File I/O http://users.csc.calpoly.edu/~gfisher/classes/102/lectures/5.html
- Java Programming - Exception Handling & Assertion - https://www3.ntu.edu.sg/home/ehchua/programming/java/J5a_ExceptionAssert.html
1.12 Exception Handling with Try (scala.utils.Try)
1.12.1 Overview
Scala provides scala.util.Try which is similar to Option type and allows failures to be dealt in higher level way than try-catch blocks.
The benefits of using Try type insted of using try-catch blocks are explicit faluire in the type system lots of useful combinators to handle failures such as map, flatMap, foreach and also for-comprehensions.
The Try algebraic data type has two type constructors, Success which holds a result or successful computation and Failure which holds a failure or exception, which is a subtype of Trowable.
sealed abstract class Try[+T] extends AnyRef case class Success[+T](value: T) extends Try[T] case class Failure[+T](exception: Throwable) extends Try[T]
Documentation
1.12.2 Examples
1.12.2.1 Example 1: basic operations.
import scala.util.{Try, Success, Failure} cala> val ma = Try{ 10 / 2} ma: scala.util.Try[Int] = Success(5) scala> val mb = Try{ 10 / 0} mb: scala.util.Try[Int] = Failure(java.lang.ArithmeticException: / by zero) //---- Map ------------ scala> ma map { x => x + 10 } res2: scala.util.Try[Int] = Success(15) scala> mb map { x => x + 10 } res3: scala.util.Try[Int] = Failure(java.lang.ArithmeticException: / by zero) //----- Foreach -------- scala> ma foreach {res => println("Result = " + res) } Result = 5 scala> mb foreach {res => println("Result = " + res) } //------ Check if it is success ---------- // scala> ma.isSuccess res16: Boolean = true scala> mb.isSuccess res17: Boolean = false //------ Check if it is failure ---------- // scala> ma.isFailure res10: Boolean = false scala> mb.isFailure res11: Boolean = true //-------- Turn result into option type ------ // scala> ma.toOption res18: Option[Int] = Some(5) scala> mb.toOption res19: Option[Int] = None //------ Get the result or failure -----------// scala> ma.get res12: Int = 5 scala> mb.get java.lang.ArithmeticException: / by zero at .$anonfun$mb$1(<console>:12) at scala.runtime.java8.JFunction0$mcI$sp.apply(JFunction0$mcI$sp.java:12) at scala.util.Try$.apply(Try.scala:209) ... 29 elided //----- Handling Result with pattern matching -------// ma match { case Success(a) => println("Successful result " + a) case Failure(e) => { println("Failure happend") println(e) } } scala> ma match { | case Success(a) | => println("Successful result " + a) | case Failure(e) | => { | println("Failure happend") | println(e) | } | } Successful result 5 mb match { case Success(a) => println("Successful result " + a) case Failure(e) => { println("Failure happend") println(e) } } scala> mb match { | case Success(a) | => println("Successful result " + a) | case Failure(e) | => { | println("Failure happend") | println(e) | } | } Failure happend java.lang.ArithmeticException: / by zero
1.12.2.2 Example 2: for-comprehensions.
import scala.util.{Try, Success, Failure} def parseAndDivide1(strA: String, strB: String) = { for { a <- Try(strA.toInt) b <- Try(strB.toInt) c <- Try (a / b) } yield c } scala> def parseAndDivide1(strA: String, strB: String) = { | for { | a <- Try(strA.toInt) | b <- Try(strB.toInt) | c <- Try (a / b) | } yield c | } parseAndDivide1: (strA: String, strB: String)scala.util.Try[Int] scala> parseAndDivide1("100", "20") res20: scala.util.Try[Int] = Success(5) scala> parseAndDivide1("100", "20x") res21: scala.util.Try[Int] = Failure(java.lang.NumberFormatException: For input string: "20x") scala> parseAndDivide1("10s0", "0") res22: scala.util.Try[Int] = Failure(java.lang.NumberFormatException: For input string: "10s0") scala> parseAndDivide1("100", "0") res23: scala.util.Try[Int] = Failure(java.lang.ArithmeticException: / by zero) scala> parseAndDivide1("100", "20") foreach { r => println("Result is equal to " + r)} Result is equal to 5 scala> parseAndDivide1("100", "20a") foreach { r => println("Result is equal to " + r)} scala> parseAndDivide1("100", "0") foreach { r => println("Result is equal to " + r)} scala> parseAndDivide1("100", "20") map { x => x * 5 } res27: scala.util.Try[Int] = Success(25) scala> parseAndDivide1("100", "0") map { x => x * 5 } res28: scala.util.Try[Int] = Failure(java.lang.ArithmeticException: / by zero) scala> parseAndDivide1("4", "a") map { x => x * 5 } res30: scala.util.Try[Int] = Failure(java.lang.NumberFormatException: For input string: "a")
1.12.2.3 Example 3: Pattern matching result.
def parseAndDivide2(strA: String, strB: String) = { val res = for { a <- Try(strA.toInt) b <- Try(strB.toInt) c <- Try (a / b) } yield c res match { case Success(a) => { println("Successful result = " + a) } case Failure(e) => { println("Failure, try again! Game over!") println(e) } } } scala> parseAndDivide2("500", "10") Successful result = 50 scala> parseAndDivide2("50d0", "10") Failure, try again! Game over! java.lang.NumberFormatException: For input string: "50d0" scala> parseAndDivide2("500", "0") Failure, try again! Game over! java.lang.ArithmeticException: / by zero
1.12.2.4 Example 4: Pattern matching specific exceptions.
def parseAndDivide3(strA: String, strB: String) = { val res = for { a <- Try(strA.toInt) b <- Try(strB.toInt) c <- Try (a / b) } yield c res match { case Success(a) => { println("Successful result = " + a) } case Failure(ex: java.lang.ArithmeticException) => { println("Failure was arithmetic exception") } case Failure(ex: java.lang.NumberFormatException) => { println("Failure was user bad input") } case Failure(e) => { println("I don't know how to deal with this kind of failure") println("Failure was \n" + e) } } } scala> parseAndDivide3("200", "50") Successful result = 4 scala> parseAndDivide3("200a", "50") Failure was user bad input scala> parseAndDivide3("200", "50x") Failure was user bad input scala> parseAndDivide3("200", "0") Failure was arithmetic exception def parseAndDivide4(strA: String, strB: String) = { val res = for { a <- Try(strA.toInt) b <- Try(strB.toInt) c <- Try (a / b) } yield c res match { case Success(a) => { println("Successful result = " + a) } case Failure(e) => e match { case ex: java.lang.ArithmeticException => { println("Failure cause was arithmetic exception.") println(e) e.printStackTrace() } case ex: java.lang.NumberFormatException => { println("Failure was user bad input.") println(ex.getMessage()) } case e => { println("Error: I don't know how to deal with this failure") println(e) } } } } scala> parseAndDivide4("200", "20") Successful result = 10 scala> parseAndDivide4("200a", "20") Failure was user bad input. For input string: "200a" scala> parseAndDivide4("200", "20x") Failure was user bad input. For input string: "20x" scala> parseAndDivide4("200", "0") Failure cause was arithmetic exception. java.lang.ArithmeticException: / by zero java.lang.ArithmeticException: / by zero at $line91.$read$$iw$$iw$$iw$$iw$.$anonfun$parseAndDivide4$5(<pastie>:20) at scala.runtime.java8.JFunction0$mcI$sp.apply(JFunction0$mcI$sp.java:12) at scala.util.Try$.apply(Try.scala:209) at $line91.$read$$iw$$iw$$iw$$iw$.$anonfun$parseAndDivide4$4(<pastie>:20) at $line91.$read$$iw$$iw$$iw$$iw$.$anonfun$parseAndDivide4$4$adapted(<pastie>:19) at scala.util.Success.flatMap(Try.scala:247) .... ... ... ... .... ... ... ...
Possibilities:
case e => { println("Error: I don't know how to deal with this failure") // Print exception println(e) // Print exception stack trace e.printStackTrace() // Print exception message println(e.getMessage()) // Throw exception again throw e }
1.12.2.5 Example 5: Try with http request
import scala.util.{Try, Success, Failure} /** Read string from byte input source InputStream and its sub classes. Note: This function doesn't close the input stream. It is supposed to be done by the caller. @param input - Byte input source @param size - Buffer size (default 1024 bytes or 1kb) @return String. */ def readInputToString(input: java.io.InputStream, size: Int = 1024): String = { val out = new java.io.ByteArrayOutputStream() // char[] buffer = new char[bufferSize] val buffer = new Array[Byte](size) // Number of bytes read var rd = 0 while( input.available() > 0){ rd = input.read(buffer) out.write(buffer, 0, rd) } out.toString("UTF-8") } def httpGetRequest(url: String): Try[String] = { import java.net.{URL, HttpURLConnection} for { urlo <- Try{new URL(url)} conn <- Try{ val c = urlo .openConnection() .asInstanceOf[java.net.HttpURLConnection] c.setRequestMethod("GET") c } is <- Try{conn.getInputStream()} out <- Try{readInputToString(is)} _ <- Try{is.close()} } yield out } def showResult[A](result: Try[A]): Unit = result match { case Success(x) => println(x) case Failure(e) => { println("Failure") println(e) } } scala> httpGetRequest("http://www.httpbin.org/get") res63: scala.util.Try[String] = Success({ "args": {}, "headers": { "Accept": "text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2", "Connection": "close", "Host": "www.httpbin.org", "User-Agent": "Java/1.8.0_144" }, "origin": "176.18.51.232", "url": "http://www.httpbin.org/get" } ) scala> httpGetRequest("https://www.httpbin.org/get") res66: scala.util.Try[String] = Success({ "args": {}, "headers": { "Accept": "text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2", "Connection": "close", "Host": "www.httpbin.org", "User-Agent": "Java/1.8.0_144" }, "origin": "176.18.51.232", "url": "https://www.httpbin.org/get" } ) scala> httpGetRequest("http://www.httpbin.org/getx") res64: scala.util.Try[String] = Failure(java.io.FileNotFoundException: http://www.httpbin.org/getx) scala> httpGetRequest("httpa://www.httpbin.org/get") res65: scala.util.Try[String] = Failure(java.net.MalformedURLException: unknown protocol: httpa) scala> httpGetRequest("http://www.fail.this.url") res67: scala.util.Try[String] = Failure(java.net.UnknownHostException: www.fail.this.url) // User disconnects in order to simulate a connection failure // scala> httpGetRequest("http://www.httpbin.org/get") res69: scala.util.Try[String] = Failure(java.net.UnknownHostException: www.httpbin.org) scala> showResult(httpGetRequest("httpx://www.httpbin.org")) Failure java.net.MalformedURLException: unknown protocol: httpx scala> showResult(httpGetRequest("http://www.httpErrorbin.org")) Failure java.net.UnknownHostException: www.httpErrorbin.org scala> showResult(httpGetRequest("http://www.httpbin.org/get")) { "args": {}, "headers": { "Accept": "text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2", "Connection": "close", "Host": "www.httpbin.org", "User-Agent": "Java/1.8.0_144" }, "origin": "176.18.51.232", "url": "http://www.httpbin.org/get" }
1.13 For comprehensions and Monads
1.13.1 Scala For-comprehensions and Haskell do-notation
1.13.1.1 For-loop
@ for (a <- 1 to 5) println(a) 1 2 3 4 5 @ for (a <- List(1, 2, 3, 4, 5)) println(a) 1 2 3 4 5 @ for {a <- List(1, 2, 3, 4, 5)}{ println("a = " + a) } a = 1 a = 2 a = 3 a = 4 a = 5 @ for (file <- new java.io.File("/").listFiles) println("file = " + file) file = /lost+found file = /boot file = /run file = /usr file = /etc file = /sbin file = /tmp file = /mnt ... ... ... ...
1.13.1.2 For-yield
Scala's for-yield or for comprehension is similar to Haskell's monad do-notation. However unlike Haskell do-notation, which is dessugarized to (>>=) bind and return functions, Scala's for-yield notation is dessugarized to flatMap, map, filter and foreach method calls.
Haskell | Scala |
---|---|
(>>=) or bind | .flatMap |
return | - Equivalent to object constructor. Option(10), Option(null), List(), List(1, 2, 3) |
fmap | .map |
forM_ | .foreach |
guard | .filter or .withFilter |
- Note: .flatMap, .map, .foreach and .filter are methods.
@ for (a <- 1 to 5) yield a res30: collection.immutable.IndexedSeq[Int] = Vector(1, 2, 3, 4, 5) @ for (a <- 1.to(5)) yield a res44: collection.immutable.IndexedSeq[Int] = Vector(1, 2, 3, 4, 5) @ for (a <- 1 to 5) yield (2 * a) res31: collection.immutable.IndexedSeq[Int] = Vector(2, 4, 6, 8, 10) @ for (file <- new java.io.File("/etc").listFiles) yield file.getName res33: Array[String] = Array( "bash.bash_logout", "papersize", "exports.d", "apparmor.d", "shadow", ... @ for { file <- new java.io.File("/etc").listFiles val data = (file.getPath, file.getName, file.length) } yield data res47: Array[(String, String, Long)] = Array( ("/etc/bash.bash_logout", "bash.bash_logout", 28L), ("/etc/papersize", "papersize", 68L), ("/etc/exports.d", "exports.d", 4096L), ("/etc/apparmor.d", "apparmor.d", 4096L), ("/etc/shadow", "shadow", 1001L), ... // // Get the name of all directories in /etc/. @ for { file <- new java.io.File("/etc").listFiles if file.isDirectory // Rejects entries that aren't file. } yield file.getName() res58: Array[String] = Array( "exports.d", "apparmor.d", "cron.weekly", "pulse", "iptables", ... @ val files = for { file <- new java.io.File("/etc").listFiles if file.isFile } yield file.getName() files: Array[String] = Array( "bash.bash_logout", "papersize", "shadow", "adjtime", "resolv.conf", ... @ files take 5 foreach println bash.bash_logout papersize shadow adjtime resolv.conf
Example 1
Get all combinations between numbers 1, 2, 3, 4 and letters 'a' and 'b'.
val c = for { a <- List(1, 2, 3, 4) b <- List('a', 'b') } yield (a, b) @ val c = for { a <- List(1, 2, 3, 4) b <- List('a', 'b') } yield (a, b) c: List[(Int, Char)] = List( (1, 'a'), (1, 'b'), (2, 'a'), (2, 'b'), (3, 'a'), ... @ c foreach println (1,a) (1,b) (2,a) (2,b) (3,a) (3,b) (4,a) (4,b)
Dessugarizing for-comprehension:
val c = { List(1, 2, 3, 4).flatMap { a => List('a', 'b').map { b => (a, b) }} } @ val c = { List(1, 2, 3, 4).flatMap { a => List('a', 'b').map { b => (a, b) }} } c: List[(Int, Char)] = List( (1, 'a'), (1, 'b'), (2, 'a'), (2, 'b'), (3, 'a'), ... @ c foreach println (1,a) (1,b) (2,a) (2,b) (3,a) (3,b) (4,a) (4,b)
Haskell Equivalent code:
> import Control.Monad(forM_) :{ xs = do a <- [1, 2, 3, 4] b <- ['a', 'b'] return (a, b) :} > :{ - xs = do - a <- [1, 2, 3, 4] - b <- ['a', 'b'] - return (a, b) - :} xs :: Num t => [(t, Char)] > xs [(1,'a'),(1,'b'),(2,'a'),(2,'b'),(3,'a'),(3,'b'),(4,'a'),(4,'b')] it :: Num t => [(t, Char)] > > mapM_ print xs (1,'a') (1,'b') (2,'a') (2,'b') (3,'a') (3,'b') (4,'a') (4,'b') it :: () > > forM_ xs print (1,'a') (1,'b') (2,'a') (2,'b') (3,'a') (3,'b') (4,'a') (4,'b') it :: () > -- Do-notation dessugarized -- :{ xs2 = [1, 2, 3, 4] >>= \ a -> ['a', 'b'] >>= \ b -> return (a, b) :} > :{ - xs2 = - [1, 2, 3, 4] >>= \ a -> - ['a', 'b'] >>= \ b -> - return (a, b) - :} xs2 :: Num t => [(t, Char)] > > xs2 [(1,'a'),(1,'b'),(2,'a'),(2,'b'),(3,'a'),(3,'b'),(4,'a'),(4,'b')] it :: Num t => [(t, Char)] > > mapM_ print xs2 (1,'a') (1,'b') (2,'a') (2,'b') (3,'a') (3,'b') (4,'a') (4,'b') it :: () > > forM_ xs2 print (1,'a') (1,'b') (2,'a') (2,'b') (3,'a') (3,'b') (4,'a') (4,'b') it :: () >
Example 2
Get all pythagorean numbers from 1 to 100. Pythagorean numbers are integers that satisfies the equation c^2 = a^2 + b^2
val pty = for { a <- 1 to 100 b <- 1 to 100 c <- 1 to 100 if (a * a + b * b == c * c) } yield (a, b, c) @ pty.take(10).foreach(println) (3,4,5) (4,3,5) (5,12,13) (6,8,10) (7,24,25) (8,6,10) (8,15,17) (9,12,15) (9,40,41) (10,24,26) @ pty take 10 foreach println (3,4,5) (4,3,5) (5,12,13) (6,8,10) (7,24,25) (8,6,10) (8,15,17) (9,12,15) (9,40,41) (10,24,26)
Dessugarizing for-comprehension:
val pty = (1 to 100).flatMap { a => (1 to 100).flatMap { b => (1 to 100).filter { c => a * a + b * b == c * c }.map{ c => (a, b, c)} }} pty: collection.immutable.IndexedSeq[(Int, Int, Int)] = Vector( (3, 4, 5), (4, 3, 5), (5, 12, 13), (6, 8, 10), (7, 24, 25), ... @ pty take 10 foreach println (3,4,5) (4,3,5) (5,12,13) (6,8,10) (7,24,25) (8,6,10) (8,15,17) (9,12,15) (9,40,41) (10,24,26)
Haskell equivalent code:
> import Control.Monad (guard) > > :t guard guard :: GHC.Base.Alternative f => Bool -> f () > :{ pty = do a <- [1 .. 100] b <- [1 .. 100] c <- [1 .. 100] guard (a * a + b * b == c * c) return (a, b, c) :} > :{ - pty = do - a <- [1 .. 100] - b <- [1 .. 100] - c <- [1 .. 100] - guard (a * a + b * b == c * c) - return (a, b, c) - :} pty :: (Eq t, Enum t, Num t) => [(t, t, t)] > > take 5 pty [(3,4,5),(4,3,5),(5,12,13),(6,8,10),(7,24,25)] it :: (Num t, Enum t, Eq t) => [(t, t, t)] > > mapM_ print (take 10 pty) (3,4,5) (4,3,5) (5,12,13) (6,8,10) (7,24,25) (8,6,10) (8,15,17) (9,12,15) (9,40,41) (10,24,26) it :: () > :{ pty2 = [1 .. 100] >>= \ a -> [1 .. 100] >>= \ b -> [1 .. 100] >>= \ c -> guard (a * a + b * b == c * c) >>= \ _ -> return (a, b, c) :} > take 5 pty2 [(3,4,5),(4,3,5),(5,12,13),(6,8,10),(7,24,25)] it :: (Num t, Enum t, Eq t) => [(t, t, t)] > > mapM_ print (take 10 pty) (3,4,5) (4,3,5) (5,12,13) (6,8,10) (7,24,25) (8,6,10) (8,15,17) (9,12,15) (9,40,41) (10,24,26) it :: () > > :t (>>=) (>>=) :: Monad m => m a -> (a -> m b) -> m b > > let bind ma fn = ma >>= fn bind :: Monad m => m a -> (a -> m b) -> m b > :{ pty3 = bind [1 .. 100] (\ a -> bind [1 .. 100] (\ b -> bind [1 .. 100] (\ c -> bind (guard (a * a + b * b == c * c)) (\ _ -> return (a, b, c) )))) :} > :{ - pty3 = - bind [1 .. 100] (\ a -> - bind [1 .. 100] (\ b -> - bind [1 .. 100] (\ c -> - bind (guard (a * a + b * b == c * c)) (\ _ -> - return (a, b, c) - )))) - :} pty3 :: (Eq t, Enum t, Num t) => [(t, t, t)] > > take 5 pty3 [(3,4,5),(4,3,5),(5,12,13),(6,8,10),(7,24,25)] it :: (Num t, Enum t, Eq t) => [(t, t, t)] > :{ pty4 = [1 .. 100] `bind` \ a -> [1 .. 100] `bind` \ b -> [1 .. 100] `bind` \ c -> guard (a * a + b * b == c * c) `bind` \ _ -> return (a, b, c) :} > :{ - pty4 = - [1 .. 100] `bind` \ a -> - [1 .. 100] `bind` \ b -> - [1 .. 100] `bind` \ c -> - guard (a * a + b * b == c * c) `bind` \ _ -> - return (a, b, c) - :} pty4 :: (Eq t, Enum t, Num t) => [(t, t, t)] > > take 5 pty4 [(3,4,5),(4,3,5),(5,12,13),(6,8,10),(7,24,25)] it :: (Num t, Enum t, Eq t) => [(t, t, t)] >
1.13.2 Options for comprehensions
For - comprehension is equivalent to method .foreach
scala> val ma : Option[Int] = Some(100) ma: Option[Int] = Some(100) scala> val mb : Option[Int] = None mb: Option[Int] = None scala> ma foreach println 100 scala> mb foreach println scala> for (x <- ma) println("x = " + x) x = 100 scala> for (x <- mb) println("x = " + x) scala> scala> for {a <- Option(System.getProperty("java.home"))} println("Java Home = " + a) Java Home = /home/archbox/opt/jdk1.8.0_144/jre scala> for {a <- Option(System.getProperty("java.home error"))} println("Java Home = " + a) scala> Option(System.getProperty("java.home")) foreach { a => println("Java Home = " + a) } Java Home = /home/archbox/opt/jdk1.8.0_144/jre scala> Option(System.getProperty("java.home error")) foreach { a => println("Java Home = " + a) }
For-yield equivalence to map
scala> val ma : Option[Int] = Some(100) ma: Option[Int] = Some(100) scala> val mb : Option[Int] = None mb: Option[Int] = None scala> for (x <- ma) yield 5 * x res34: Option[Int] = Some(500) scala> for (x <- mb) yield 5 * x res35: Option[Int] = None scala> ma map (x => 5 * x) res36: Option[Int] = Some(500) scala> mb map (x => 5 * x) res37: Option[Int] = None
For-yield expressions for Option type are a syntax sugar for nested flatMaps and maps method calls similar to Haskell's Maybe monad.
def addSafe1(ma: Option[Int], mb: Option[Int]) = { for { a <- ma b <- mb } yield a + b } def addSafe2(ma: Option[Int], mb: Option[Int]) = { ma.flatMap { a => mb.map { b => a + b }} } scala> addSafe1(Some(10), Some(15)) res43: Option[Int] = Some(25) scala> addSafe1(Some(10), None) res44: Option[Int] = None scala> addSafe1(None, None) res45: Option[Int] = None scala> addSafe2(Some(10), Some(15)) res46: Option[Int] = Some(25) scala> addSafe2(Some(10), None) res47: Option[Int] = None scala> addSafe2(None, None) res48: Option[Int] = None
1.13.3 Identity Monad Implementation
case class Identity[A](value: A) { def flatMap[B](fn: A => Identity[B]): Identity[B] = { fn(value) } def map[B](fn: A => B): Identity[B] = { Identity(fn(value)) } def foreach[B](fn: A => Unit): Unit = fn(value) } @ Identity(10) res65: Identity[Int] = Identity(10) @ Identity(10) map (x => x * 4) res66: Identity[Int] = Identity(40) @ Identity(10) flatMap (x => Identity(x * 4)) res67: Identity[Int] = Identity(40) @ for (x <- Identity(10)) println(x) 10 @ for (x <- Identity("Hello world")) println(x) Hello world @ for (x <- Identity(5)) yield 4 * x res70: Identity[Int] = Identity(20) @ for { a <- Identity(10) b <- Identity(25) } yield a + b res71: Identity[Int] = Identity(35)
1.13.4 Maybe/Option implementation
This example shows an Option type monad and collection implementation. It was named Maybe in order to avoid conflict with Scala's built in Option type.
file: src/maybe-collection.scala
// Sealed trait means that is not possible to add more // implementation of this trait/interface out of this file. // sealed trait Maybe[+A]{ def get(): A // Haskell's forM_ - Apply a function that returns nothing to a // collection. // def foreach(a: A => Unit): Unit // Haskell's fmap // def map[B](fn: A => B): Maybe[B] // Haskell's (>>=) bind // def flatMap[B](fn: A => Maybe[B]): Maybe[B] // Haskell's guard // def withFilter(fn: A => Boolean): Maybe[A] def filter(fn: A => Boolean) = withFilter(fn) } // Equivalent to Haskell's Nothing or Scala's None. // // case object Empty extends Maybe[Nothing]{ def get() = throw new RuntimeException("Error: Attemp to get data from Empty value") def foreach(fn: Nothing => Unit) = () def map[B](fn: Nothing => B) = Empty def flatMap[B](fn: Nothing => Maybe[B]) = Empty def withFilter(fn: Nothing => Boolean) = Empty } // Similar to Haskell's Just or Scala's Some. // // case class Just[+A](value: A) extends Maybe[A]{ def get() = value def foreach(fn: A => Unit) = fn(value) def map[B](fn: A => B) = Just(fn(value)) def flatMap[B](fn: A => Maybe[B]) = fn(value) def withFilter(fn: A => Boolean): Maybe[A] = { if(fn(value)) Just(value) else Empty } } // Companion object // object Maybe{ def apply[A](value: A) = { if (value != null) Just(value) else Empty } def makeMaybeFn[A, B](fn: A => B) = (x: A) => { apply(fn(x)) } def mapM2[A, B, C](fn: (A, B) => C, ma: Maybe[A], mb: Maybe[B]) = { for { a <- ma b <- mb } yield fn(a, b) } } def addSafe(sa: Maybe[Int], sb: Maybe[Int]) = { for { a <- sa b <- sb } yield a + b } def readNumber(prompt: String) = { try { print(prompt) val sc = new java.util.Scanner(System.in) val n = Just(sc.nextInt) print("\n") n } catch { case ex: java.util.InputMismatchException => Empty } } def parseInt(str: String): Maybe[Int] = { try Just(str.toInt) catch { case ex: java.lang.NumberFormatException => Empty } }
Test:
scala> :paste src/maybe-collection.scala Pasting file src/maybe-collection.scala... defined trait Maybe defined object Empty defined class Just defined object Maybe addSafe: (sa: Maybe[Int], sb: Maybe[Int])Maybe[Int] readNumber: (prompt: String)Product with Serializable with Maybe[Int] parseInt: (str: String)Maybe[Int] //------- Test function apply ---------------- // scala> Maybe(null) : Maybe[String] res5: Maybe[String] = Empty scala> (Maybe(null) : Maybe[String]) foreach println scala> Maybe("Hello") foreach println Hello scala> //------ tet map, foreach, and get ------ // scala> val ma: Maybe[Int] = Empty ma: Maybe[Int] = Empty scala> val mb: Maybe[Int] = Just(100) mb: Maybe[Int] = Just(100) scala> for (x <- ma) println("x = " + x) scala> for (x <- mb) println("x = " + x) x = 100 scala> for (x <- ma) yield 5 * x res15: Maybe[Int] = Empty scala> for (x <- mb) yield 5 * x res16: Maybe[Int] = Just(500) scala> ma map (x => x * 5) res18: Maybe[Int] = Empty scala> mb map (x => x * 5) res19: Maybe[Int] = Just(500) //------- Test makeMaybeFn ---------- // // This static method, that can be regarded as function, returns null // which can unexpectedly crash the program. // scala> System.getProperty("java.home") res21: String = /home/archbox/opt/jdk1.8.0_144/jre scala> System.getProperty("java.homex") res22: String = null scala> val getPropertySafe = Maybe.makeMaybeFn(System.getProperty) scala> getPropertySafe("java.homex") res23: Product with Serializable with Maybe[String] = Empty scala> getPropertySafe("java.home") res24: Product with Serializable with Maybe[String] = Just(/home/archbox/opt/jdk1.8.0_144/jre) scala> getPropertySafe("java.home") foreach println /home/archbox/opt/jdk1.8.0_144/jre scala> getPropertySafe("java.homex") foreach println //-------- Test function addSafe --------- scala> addSafe(parseInt("200"), parseInt("300")) res27: Maybe[Int] = Just(500) scala> addSafe(parseInt("200"), parseInt("300x")) res28: Maybe[Int] = Empty scala> addSafe(parseInt("20s0"), parseInt("300x")) res29: Maybe[Int] = Empty
1.13.5 IO Monad Implementation
File: src/iomonad.scala
class IO[A](action: => A){ import scala.util.{Try, Success, Failure} def run() = action def map[B](fn: A => B): IO[B] = { new IO(fn(action)) } def flatMap[B](fn: A => IO[B]): IO[B] = { new IO(fn(action).run()) } // Retry until successful def retry() = { def aux(): A = { Try{action} match { case Success(a) => a case _ => aux() } } new IO(aux()) } def forever() = { def aux(){ this.run() aux() } new IO(aux()) } def doTimes(n: Int) = { val io = this new IO(for (i <- 1 to n) this.run()) } } // ---- End of class IO ----- // object IO{ def apply[A](action: => A) = new IO(action) val readLine = new IO(scala.io.StdIn.readLine()) val readDouble = new IO(scala.io.StdIn.readDouble()) val readInt = new IO(scala.io.StdIn.readInt()) def printn[A](a: A) = new IO(Predef.println(a)) def print[A](a: A) = new IO(Predef.print(a)) def prompt(msg: String): IO[String] = for { _ <- print(msg) line <- readLine } yield line } def promptParse[A](msg: String, fn: String => A): IO[A] = for { _ <- IO.print(msg) line <- IO.readLine _ <- IO.printn("") } yield fn(line) val getNumAndSum: IO[Int] = for { x <- promptParse("Enter x: ", _.toInt).retry y <- promptParse("Enter y: ", _.toInt).retry } yield x + y val main = for { _ <- IO.printn("\nTesting IO action getNumAndSum") x <- getNumAndSum _ <- IO.print("The result is = " + x) } yield () // Uncomment the line below to run the program. main.forever().run()
Running:
$ scala -save iomonad.scala Testing IO action getNumAndSum Enter x: 200 Enter y: sadsa Enter y: 300 The result is = 500 Testing IO action getNumAndSum Enter x: 500 Enter y: 200 The result is = 700 Testing IO action getNumAndSum Enter x: sadsa Enter x: xhss Enter x: Enter x: Enter x: 89 Enter y: 34 The result is = 123 Testing IO action getNumAndSum Enter x: 100 Enter y: 200 The result is = 300 # Enter Ctrl + C to end the program
Experiment in the REPL:
- This step requires the last line of the file to commented.
scala> :paste iomonad.scala Pasting file iomonad.scala... defined class IO defined object IO promptParse: [A](msg: String, fn: String => A)IO[A] getNumAndSum: IO[Int] = IO@388623ad main: IO[Unit] = IO@91f565d scala> IO.printn("Testing IO monad") res1: IO[Unit] = IO@3434a4f0 scala> IO.printn("Testing IO monad") doTimes(5) res2: IO[Unit] = IO@53ea380b scala> IO.printn("Testing IO monad") doTimes(5) run() Testing IO monad Testing IO monad Testing IO monad Testing IO monad Testing IO monad scala> val action = promptParse("Enter a valid double: ", _.toDouble) action: IO[Double] = IO@2336cd91 // User types 12345 //------------------- scala> action.run() Enter a valid double: res10: Double = 12345.0 // User types "error" //----------------- scala> action.run() Enter a valid double: java.lang.NumberFormatException: For input string: "error" at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:2043) at sun.misc.FloatingDecimal.parseDouble(FloatingDecimal.java:110) at java.lang.Double.parseDouble(Double.java:538) ... ... ... ... // Keep running IO action until user provides a valid input //----------------------- scala> action.retry() res4: IO[Double] = IO@6bd28e4a scala> action.retry().run() Enter a valid double: Enter a valid double: Enter a valid double: res5: Double = 12345.0 // User types 100 //----------------------------- scala> action.retry() map (x => x * 4.0) run() Enter a valid double: res7: Double = 400.0 scala> action.map((x: Double) => x * 4.0).flatMap{ n => IO.printn("Result = " + n)} res12: IO[Unit] = IO@37e0614e scala> val action2 = action.map((x: Double) => x * 4.0).flatMap{ n => IO.printn("Result = " + n)} action2: IO[Unit] = IO@3e5d10fc // User types 45 scala> action2.run() Enter a valid double: Result = 180.0 // User types 'error' in order to crash the program. scala> action2.run() Enter a valid double: java.lang.NumberFormatException: For input string: "error" at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:2043) at sun.misc.FloatingDecimal.parseDouble(FloatingDecimal.java:110) at java.lang.Double.parseDouble(Double.java:538) // The IO action runs until user provides a valid input. //------------------------------ scala> action2 retry() run() Enter a valid double: Enter a valid double: Enter a valid double: Result = 60.0 val action3 = for { x <- action n = 4.0 * x _ <- IO.printn("Result = " + n) } yield() scala> val action3 = for { | x <- action | n = 4.0 * x | _ <- IO.printn("Result = " + n) | } yield() action3: IO[Unit] = IO@22b9f45f scala> action3.run() Enter a valid double: Result = 48.0 scala> action3.run() Enter a valid double: java.lang.NumberFormatException: For input string: "crash" at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:2043) at sun.misc.FloatingDecimal.parseDouble(FloatingDecimal.java:110) at java.lang.Double.parseDouble(Double.java:538) // Run IO action forever until user types Ctrl+C // ending the execution. // scala> action3 retry() forever() run() Enter a valid double: Enter a valid double: Enter a valid double: Result = 496.0 Enter a valid double: Result = 140.0 Enter a valid double: Result = 48.0 Enter a valid double: Result = 20.0
1.13.6 Reader Monad Implementation
case class Reader[R, A](run: R => A){ // Haskell fmap or <$> operator // def map[B](fn: A => B): Reader[R, B] = Reader(r => fn(this.run(r))) // Haskell bind (>>=) // def flatMap[B](fn: A => Reader[R, B]): Reader[R, B] = Reader(r => fn(this.run(r)).run(r)) def withReader[Z](fn: Z => R): Reader[Z, A] = Reader(z => this.run(fn(z))) def local(fn: R => R): Reader[R, A] = Reader(r => this.run(fn(r))) def foreach(fn: A => Unit): Reader[R, Unit] = Reader(r => fn(this.run(r))) // Haskell - runReader def apply(x: R): A = run(x) } object Reader{ // Equivalent of Haskell return :: a -> (Reader r) a def pure[R, A](x: A) = Reader((_: R) => x) def ask[R] = Reader[R, R]((r: R) => r) }
Testing:
1.13.7 References
- [H|SD] - How does yield work? | Scala Documentation. Available at https://docs.scala-lang.org/tutorials/FAQ/yield.html
1.14 OOP - Object Oriented Programming
1.14.1 Everything is an object
In Scala everything is an object:
- Tuples are objects.
scala> val t1 = (10, "20.2") t1: (Int, String) = (10,20.2) scala> t1._1 res47: Int = 10 scala> t1._2 res48: String = 20.2 scala> val t2 = new Tuple2(10, "20.2") t2: (Int, String) = (10,20.2) scala> t2._1 res49: Int = 10 scala> t2._2 res50: String = 20.2 scala> t1 == t2 res51: Boolean = true scala> val t3 = new Tuple3(10, "aa", 20.4) t3: (Int, String, Double) = (10,aa,20.4) scala> t3._1 res59: Int = 10 scala> t3._2 res60: String = aa scala> t3._3 res61: Double = 20.4
- Functions are objects
val f1 = new Function1[Int, String]{ def apply(x: Int) = (2 * x + 10).toString } scala> val f1 = new Function1[Int, String]{ | def apply(x: Int) = (2 * x + 10).toString | } f1: Int => String = <function1> scala> f1(10) res52: String = 30 scala> f1(6) res53: String = 22 val f2 = new Function2[Double, Double, Double]{ def apply(x: Double, y: Double) = 2 * x + y } scala> val f2 = new Function2[Double, Double, Double]{ | def apply(x: Double, y: Double) = 2 * x + y | } f2: (Double, Double) => Double = <function2> scala> f2(2.0, 5.0) res54: Double = 9.0 scala> f2(5.0, 2.0) res55: Double = 12.0
- Numbers are objects and operators (+), (-), (/) are not operators, they are methods.
scala> 10 + 5 res56: Int = 15 scala> 10.+(5) res57: Int = 15 scala> List(1, 2, 3, 4).map(a => 10.+(a)) res58: List[Int] = List(11, 12, 13, 14)
1.14.2 Case classes as records
Example: Simple record type.
scala> case class Person(id: Int, firstName: String, lastName: String) defined class Person scala> val p1 = Person(100, "John", "Smith") p1: Person = Person(100,John,Smith) scala> val p2 = Person(600, "Isaac", "Newton") p3: Person = Person(600,Isaac,Newton) scala> p1.lastName res150: String = Smith scala> p1.lastName = "" <console>:16: error: reassignment to val p1.lastName = "" ^ scala> List(p1, p2).foreach(println) Person(100,John,Smith) Person(600,Isaac,Newton) scala> List(p1, p2).map(_.firstName) res167: List[String] = List(John, Isaac) scala> List(p1, p2).map(_.lastName) res168: List[String] = List(Smith, Newton) scala>
1.14.3 Special Methods - Apply and Update
1.14.3.1 Apply method - Callable objects
The Scala Apply-method is used to create callable objects similar to C++'s "functor" (aka function-object) or an object that behaves like function.
Example 1 - The apply method can be overloaded as a long as each version has a unique type signature.
class ApplyTest{ def apply(x: Int) = println("Apply called with integer - x = " + x) def apply(s: String) = println(s"Apply called with string - s = '$s' ") def apply(x: Double, y: Double) = println("x + y = " + (x + y)) } scala> obj1(10) Apply called with integer - x = 10 scala> obj1.apply(10) Apply called with integer - x = 10 scala> obj1("hello world Scala, C++ functor function object") Apply called with string - s = 'hello world Scala, C++ functor function object' scala> obj1 apply "hello world Scala, C++ functor function object" Apply called with string - s = 'hello world Scala, C++ functor function object' scala> obj1(5.6, 2.435) x + y = 8.035 scala> obj1 apply (5.6, 2.435) x + y = 8.035 scala> obj1.apply(5.6, 2.435) x + y = 8.035
Example 2 - Singleton objects can also have apply method and be called like functions.
object SingletonModule { def apply(x: Int) = println("Apply called with integer - x = " + x) def apply(s: String) = println(s"Apply called with string - s = '$s' ") def apply(x: Double, y: Double) = println("x + y = " + (x + y)) } scala> SingletonModule.apply(5) Apply called with integer - x = 5 scala> SingletonModule.apply("C++'s function object") Apply called with string - s = 'C++'s function object' scala> SingletonModule("C++'s function object") Apply called with string - s = 'C++'s function object' scala> SingletonModule(3.5, 9.343) x + y = 12.843 scala> SingletonModule.apply(3.5, 9.343) x + y = 12.843 scala> SingletonModule apply (3.5, 9.343) x + y = 12.843
Example 3
class LinearEquation(a: Double = 0, b: Double = 0){ private var (_a, _b) = (a, b) override def toString() = f"Linear equation = ${_a}%.3f * x + ${_b}%.3f " def setA(a: Double) = _a = a def setB(b: Double) = _b = b // Compute the equation Y(x) = A * x + B at point x def compute(x: Double) = _a * x + _b def apply(x: Double) = _a * x + _b }
The object lineq can be called as it was a function which is a syntax sugar for the apply method.
// Equation 3 * x + 4 scala> val lineq = new LinearEquation(3, 4) lineq: LinearEquation = Linear equation = 3.000 * x + 4.000 // 3 * 0 + 4 = 0 // --> It is a syntax sugar for the method apply. //------------------------------------------- scala> lineq(0) res40: Double = 4.0 scala> lineq.apply(0) res50: Double = 4.0 scala> lineq.compute(0.0) res47: Double = 4.0 // 3 * 1 + 4 = 7 //------------------------------------------- scala> lineq(1) res41: Double = 7.0 scala> lineq.apply(1.0) res51: Double = 7.0 scala> lineq.compute(1) res48: Double = 7.0 // 3 * 2 + 4 = 6 //------------------------------------------- scala> lineq(2) res42: Double = 10.0 scala> lineq.apply(2) res52: Double = 10.0 scala> lineq.compute(2) res49: Double = 10.0 scala> List(1.0, 2.0, 3.0, 4.0) map lineq <console>:13: error: type mismatch; found : LinearEquation required: Double => ? List(1.0, 2.0, 3.0, 4.0) map lineq scala> List(1.0, 2.0, 3.0, 4.0) map {lineq (_)} res64: List[Double] = List(7.0, 10.0, 13.0, 16.0) scala> List(1.0, 2.0, 3.0, 4.0) map lineq.compute res65: List[Double] = List(7.0, 10.0, 13.0, 16.0) // Change the coefficients to Y(x) = 5 * x + 0 scala> lineq.setA(5) scala> lineq.setB(0) scala> List(1.0, 2.0, 3.0, 4.0) map {lineq(_)} res68: List[Double] = List(5.0, 10.0, 15.0, 20.0)
1.14.3.2 Callable object comparison with closures
A closure function could be used instead of a callable object providing the same results. The difference is that the objects can update their internal state at run-time while a closures cannot.
def makeLinearEq(a: Double, b: Double) = (x: Double) => a * x + b scala> def makeLinearEq(a: Double, b: Double) = | (x: Double) => a * x + b makeLinearEq: (a: Double, b: Double)Double => Double // Equation: Y(x) = 3 * x + 4 generated with closure scala> val lineqClosure = makeLinearEq(3.0, 4.0) lineqClosure: Double => Double = $$Lambda$1577/1928574577@1ccc5740 scala> lineqClosure(1.0) res70: Double = 7.0 scala> lineqClosure(2.0) res72: Double = 10.0 scala> List(1.0, 2.0, 3.0, 4.0) map lineqClosure res71: List[Double] = List(7.0, 10.0, 13.0, 16.0) // There is no way to update a and b to a = 5 and b = 0 // keeping the original lambda function. The only way to accomplish this // is to generate a new function. scala> val lineqClosure2 = makeLinearEq(5.0, 0.0) lineqClosure2: Double => Double = $$Lambda$1577/1928574577@36715f68 scala> List(1.0, 2.0, 3.0, 4.0) map lineqClosure2 res73: List[Double] = List(5.0, 10.0, 15.0, 20.0) scala> val lineq = new LinearEquation(3, 4) lineq: LinearEquation = Linear equation = 3.000 * x + 4.000 scala> List(1.0, 2.0, 3.0, 4.0) map { lineq (_) } res74: List[Double] = List(7.0, 10.0, 13.0, 16.0) // A Callable object can update its internal state at run-time. // scala> lineq.setA(5) scala> lineq.setB(0) scala> lineq res79: LinearEquation = Linear equation = 5.000 * x + 0.000 scala> List(1.0, 2.0, 3.0, 4.0) map { lineq (_) } res80: List[Double] = List(5.0, 10.0, 15.0, 20.0) scala> List(1.0, 2.0, 3.0, 4.0) map lineq.compute res82: List[Double] = List(5.0, 10.0, 15.0, 20.0)
Despite the closure limitations, the callable-object approach could be emulated with closures by returning multiple functions from the top-level function.
// Case class is used here to emulate a record type case class LinearEquationFn( setA: Double => Unit, getA: () => Double, setB: Double => Unit, getB: () => Double, compute: Double => Double ) { override def toString() = { val (_a, _b) = (getA(), getB()) f"Linear equation = ${_a}%.3f * x + ${_b}%.3f " } } def makeLinearEqFn(a: Double, b: Double) = { type D = Double var (_a, _b) = (a, b) val setB = (b: D) => {_b = b} LinearEquationFn( setA = (a: D) => {_a = a }, getA = () => _a, setB = setB, getB = () => _b, compute = (x: D) => _a * x + _b ) } scala> val lineqEsoteric = makeLinearEqFn(3.0, 4.0) lineqEsoteric: LinearEquationFn = Linear equation = 3.000 * x + 4.000 scala> lineqEsoteric.getA() res83: Double = 3.0 scala> lineqEsoteric.getB() res84: Double = 4.0 scala> lineqEsoteric.compute(2) res85: Double = 10.0 scala> List(1.0, 2.0, 3.0, 4.0) map lineqEsoteric.compute res86: List[Double] = List(7.0, 10.0, 13.0, 16.0) scala> lineqEsoteric.setA(5.0) scala> lineqEsoteric.setB(0.0) scala> lineqEsoteric compute 5.0 res90: Double = 25.0 scala> List(1.0, 2.0, 3.0, 4.0) map lineqEsoteric.compute res89: List[Double] = List(5.0, 10.0, 15.0, 20.0)
1.14.3.3 Update method
The update method provides an assignment syntax sugar which can be useful for DSLs.
Example
class TestUpdate{ private var _x = 0 def apply() = println("Value of x is = " + _x) def update(x: Int) = { _x = x println("x set to " + _x) } def update(x: Int, y: Int) = { _x = x + y println("x set to " + _x) } def update(s: String, value: Object) = { println(s"Key $s of hash table set to value = $value") } }
Testing:
scala> val u = new TestUpdate() u: TestUpdate = TestUpdate@16f1cb2d scala> u() Value of x is = 0 scala> u() = 10 x set to 10 scala> u() Value of x is = 10 scala> u.update(35) x set to 35 scala> u() Value of x is = 35 scala> u.apply() Value of x is = 35 scala> scala> u.update(5, 10) x set to 15 scala> u() Value of x is = 15 scala> u("database.url") = "blob" Key database.url of hash table set to value = blob scala> u.update("database.url", "blob") Key database.url of hash table set to value = blob u("character.gui") = new javax.swing.JFrame() Key character.gui of hash table set to value = javax.swing.JFrame[frame0 ... scala> u.update("character.gui", new javax.swing.JFrame()) Key character.gui of hash table set to value = javax.swing.JFrame ... ... .
1.14.3.4 Implementing C#-like properties
The update and apply method can be used to create C#-like properties which are shorthands for get and set methods. A motivation to use property or get or setters instead of public fields is the encapsulation as making the internal data private allows changing the class code, the data representation, memory layout; adding validation logic to setters and firing events to GUIs without changing the client code or breaking third-party code.
// Only the get and set abstract methods needs to be defined. trait IProperty[A]{ def get(): A def set(x: A): Unit def apply() = get() def update(x: A) = set(x) def :=(x: A) = set(x) override def toString() = get().toString } object Property{ def simple[A](init: A) = { var _x = init new IProperty[A]{ def get() = _x def set(x: A) = {_x = x} } } def fromGS[A](getter: => A, setter: A => Unit) = new IProperty[A]{ def get() = getter def set(x: A) = setter(x) } def readOnly[A](getter: => A) = new IProperty[A]{ def get() = getter def set(x: A) = throw new java.lang.IllegalArgumentException("Error: Read-only property.") } def validate(cond: Boolean, msg: String = "Error invalid argument")(action: => Unit) = if(cond) action else throw new java.lang.IllegalArgumentException(msg) }
Testing and use-case:
class Product{ private var _name = "unnamed" private var _price = 0.0 val id = Property.simple(0) val name = Property.fromGS( getter = _name, setter = (name: String) => Property.validate(name != "", "Name cannot be empty"){ println("Name set to " + name) _name = name } ) val price = Property.fromGS( getter = _price, setter = (price: Double) => { Property.validate(price > 0 , "Price cannot be negative"){ println("Price set to " + price) _price = price } } ) } scala> val product = new Product() product: Product = Product@209f6b54 scala> product.id res20: IProperty[Int] = 0 scala> product.id() res21: Int = 0 scala> product.id() = 10 scala> product.id res23: IProperty[Int] = 10 scala> product.id := 20 scala> product.id res25: IProperty[Int] = 20 // ----- Name property scala> product.name res18: IProperty[String] = unnamed cala> product.name() = "coffee" Name set to coffee scala> product.name.get() res32: String = coffee scala> product.name.set("fresh orange juice") Name set to fresh orange juice scala> product.name := "Chilean wine" Name set to Chilean wine scala> product.name() = "" java.lang.IllegalArgumentException: Name cannot be empty at Property$.validate(<pastie>:49) at Product.$anonfun$name$2(<pastie>: //---- Price property scala> product.price res19: IProperty[Double] = 0.0 scala> product.price() = 10.0 Price set to 10.0 scala> product.price := 20.0 Price set to 20.0 scala> product.price.set(15.0) Price set to 15.0 scala> product.price() * 10 res42: Double = 150.0 scala> product.price.get * 10.0 res47: Double = 150.0 scala> product.price := -10.0 java.lang.IllegalArgumentException: Price cannot be negative at Property$.validate(<pastie>:49) at Product.$anonfun$price$2(<pastie>:29) at scala.runtime.java8.JFunction1$mcVD$sp.apply(JFunction1$mcVD$sp.java:12) at Property$$anon$2.set(<pastie>:34) at IProperty.$colon$eq(<pastie>:18) at IProperty.$colon$eq$(<pastie>:18) at Property$$anon$2.$colon$eq(<pastie>:32) ... 28 elided def showProduct(prod: Product) = { println("Product info") println(" id = " + prod.id) println(" name = " + prod.name) println(" price = " + prod.price) } scala> showProduct(product) Product info id = 20 name = Chilean wine price = 15.0 def updateProperty[A](prop: IProperty[A], fn: A => A) = prop() = fn(prop()) // Adjust price to inflation rate 6.5% scala> updateProperty(product.price, (x: Double) => (100.0 + 6.5) / 100.0 * x) Price set to 15.975
See also:
1.14.4 Creating a Class
class Account(owner: String, id: Int, balanceInit: Int) { private var balance = balanceInit def getBalance() = balance def getId() = id def getOwner() = owner def deposit(amount: Int) = { if (amount < 0) error("Erro: Invalid operation. Negative amount of money") else balance = balance + amount } def withdraw(amount: Int) = amount match { case a if a > balance => error("Error: Not enough funds to withdraw.") case a if a < 0 => error("Erro: Invalid operation. Negative amount of money") case _ => balance = balance - amount } override def toString() = { s"Account{id = $id, owner = $owner} = $$ $balance" } } // End of class Account scala> val account = new Account("Joseph Smith", 10234, 4000) account: Account = Account{id = 10234, owner = Joseph Smith} = $ 4000 // type tab after dot (.) to show all fields and methods // scala> account. deposit getBalance getId getOwner toString withdraw scala> account.deposit _ res228: Int => Unit = <function1> scala> account.getBalance _ res229: () => Int = <function0> scala> account.getId _ res230: () => Int = <function0> scala> account.toString() res180: String = Account{id = 10234, owner = Joseph Smith} = $ 4000 scala> account.getClass() res181: Class[_ <: Account] = class Account scala> account.withdraw(300) scala> account res188: Account = Account{id = 10234, owner = Joseph Smith} = $ 3700 scala> account.withdraw(10000) java.lang.RuntimeException: Error: Not enough funds to withdraw. at scala.sys.package$.error(package.scala:27) at scala.Predef$.error(Predef.scala:144) at Account.withdraw(<console>:24) ... 32 elided scala> account.withdraw(-100) java.lang.RuntimeException: Erro: Invalid operation. Negative amount of money at scala.sys.package$.error(package.scala:27) at scala.Predef$.error(Predef.scala:144) at Account.withdraw(<console>:27) ... 32 elided scala> account res191: Account = Account{id = 10234, owner = Joseph Smith} = $ 3700 scala> account.deposit(400) scala> account res193: Account = Account{id = 10234, owner = Joseph Smith} = $ 4100 scala> account.deposit(-400) java.lang.RuntimeException: Erro: Invalid operation. Negative amount of money at scala.sys.package$.error(package.scala:27) at scala.Predef$.error(Predef.scala:144) at Account.deposit(<console>:17) ... 32 elided scala> account.getOwner() res225: String = Joseph Smith scala> account.getId() res226: Int = 10234
1.14.5 Inheritance
Inheritance is useful in GUI programming to create new Widgets derived from existing ones.
The Button that inherits JButton below has a better initialization and Scala-friendly method to add event handlers (aka Java's listeners).
- The method onClick returns a function that when executed function with type synonym dispose that when executed removes the event handler.
import javax.swing._ type Dispose = () => Unit class Button( text: String, enabled: Boolean = true, bgColor: java.awt.Color = null, fgColor: java.awt.Color = null, toolTip: String = null, onClick: => Unit = () ) extends javax.swing.JButton { init() private def init(){ this.setText(text) this.setEnabled(enabled) if (bgColor != null) this.setBackground(bgColor) if (fgColor != null) this.setForeground(fgColor) if (toolTip != null) this.setToolTipText(toolTip) this.onClick{ onClick } } def onClick (handler: => Unit): Dispose = { val listener = new java.awt.event.ActionListener(){ def actionPerformed(evt: java.awt.event.ActionEvent) = { handler } } this.addActionListener(listener) () => this.removeActionListener(listener) } } val frame = new JFrame("Hello world") frame.setSize(400, 300) frame.setLayout(new java.awt.FlowLayout()) val button1 = new Button( "Click me!", // Optional parameters fgColor = java.awt.Color.RED, // Foreground color bgColor = java.awt.Color.GRAY, // Background color toolTip = "Please click at me" ) val button2 = new Button("Button Exit") frame.add(button1) frame.add(button2) frame.setVisible(true) button1.onClick{ println("Button 1 clicked") } button2.onClick{ System.exit(0)}
1.14.6 Class with higher order methods
Scala classes can have methods that accepts functions as parameters or higher order methods. Note that Scala's collections like List, Array, Map have higher order methods such as Array.map, Array.foreach and so on.
Example:
class Pipe[A](value: A){ def p[B](fn: A => B) = new Pipe(fn(value)) def get() = value } scala> val v = new Pipe(100.0) v: Pipe[Double] = Pipe@d8c6f0 scala> v.p(_+10).get res41: Double = 110.0 scala> v.p(_+10).p(Math.sin).get res42: Double = -0.044242678085070965 scala> v.p(_+10).p(Math.sin).p(Math.cos).get res43: Double = 0.9990214523521718 scala> v.p(_+10).p(Math.sin).p(Math.cos).p(x => x * 3).get res44: Double = 2.9970643570565154 // Alternative way scala> v p(_+10) get res47: Double = 110.0 scala> v p(_+10) p(Math.sin) p(Math.cos) p(x => x * 3) get res48: Double = 2.9970643570565154 { v .p(_+10) .p(Math.sin) .p(Math.cos) .p(x => x * 3) .get } scala> { v | .p(_+10) | .p(Math.sin) | .p(Math.cos) | .p(x => x * 3) | .get | } res49: Double = 2.9970643570565154 val out = { v .p(_+10) .p(Math.sin) .p(Math.cos) .p(x => x * 3) .get } out: Double = 2.9970643570565154
1.14.7 "Operator overloading"
Note: Scala doesn't have operator overloading since (+), (-) and other math operators are just methods.
Example: Complex number arithmetic.
package complex case class Cpl(re: Double, img: Double){ override def toString() = s"${re} + ${img}j" def unary_-() = Cpl(-this.re, -this.img) def +(that: Double) = Cpl(this.re + that, this.img) def +(that: Cpl) = Cpl(this.re + that.img, this.img + that.img) def -(that: Double) = Cpl(this.re - that, this.img) def -(that: Cpl) = Cpl(this.re - that.re, this.img - that.img) def *(that: Double) = Cpl(that * this.re, that * this.img) def *(that: Cpl) = { val x = this.re * that.re - this.img * that.img val y = this.re * that.img + this.img * that.re Cpl(x, y) } def /(that: Double) = Cpl(this.re / that, this.img / that) def /(that: Cpl) = { val r = that.re * that.re + that.img * that.img val x = this.re * that.re + this.img * that.img val y = - this.re * that.img + this.img * that.re Cpl(x / r, y / r) } // Conjugate def conj = Cpl(this.re, -this.img) def norm = Math.sqrt(re * re + img * img) // Angle in radians def angle = Math.atan2(img, re) // Angle in degrees def angled = Math.atan2(img, re) * 180.0 / Math.PI } object Complex{ val j = Cpl(0, 1) implicit class DoubleToCpl(k: Double) { //def j = Cpl(0, k) def polr = Cpl(Math.cos(k), Math.sin(k)) def pold = { val a = k / 180.0 * Math.PI Cpl(Math.cos(a), Math.sin(a)) } // k + that def +(that: Cpl) = Cpl(that.re + k, that.img) // k * that def *(that: Cpl) = Cpl(k * that.re, k * that.img) def -(that: Cpl) = Cpl(k - that.re, -that.img) // k / that def /(that: Cpl) = { val c = that.re * that.re + that.img * that.img Cpl( k * that.re / c, - k * that.img / c) } } }
Explanation:
The expression 4.56 + (10 + 4j) is equivalent to 4.56.add(10 + 4j), that is a Double class method invocation. But implementing the method .add(<complex>) would require modifying the source code of the class Double. As it is not possible, the only way to implement this method is by creating an implicit class "DoubleToCpl" that works as follow: whenever the compiler finds the expression 4.50 + (10 + 4j), it converts the number 4.50, that is an instance of Double class, to the class DoubleTocpl that has a method to carry out complex number addition.
- (10 + 4j) + 3.50 is the same as:
- (10 + 4j) add 3.50 or (10 + 4j).add(3.50)
- 4.56 + (10 + 4j) is the same as:
- 4.56 add (10 + 4j) or 4.56.add(10 + 4j)
Testing in the REPL by loading the script.
scala> :paste src/complex.scala Pasting file src/complex.scala... import complex.Cpl import complex.Complex._ import scala.language.postfixOps // Imaginary unit //------------------------ // scala> j res0: complex.Cpl = 0.0 + 1.0j scala> j * j res1: complex.Cpl = -1.0 + 0.0j scala> - j res2: complex.Cpl = -0.0 + -1.0j scala> 6 + 8 * j res3: complex.Cpl = 6.0 + 8.0j scala> val c = 6 + 8 * j c: complex.Cpl = 6.0 + 8.0j scala> c.re res4: Double = 6.0 scala> c.img res5: Double = 8.0 scala> -c res9: complex.Cpl = -6.0 + -8.0j scala> c.conj res10: complex.Cpl = 6.0 + -8.0j scala> c * c.conj res12: complex.Cpl = 100.0 + 0.0j scala> 5 * c res14: complex.Cpl = 30.0 + 40.0j scala> c / 2 res15: complex.Cpl = 3.0 + 4.0j scala> Cpl(3.0, 4.0) res16: complex.Cpl = 3.0 + 4.0j scala> Cpl(3.0, 4.0).img res17: Double = 4.0 scala> Cpl(3.0, 4.0).norm res18: Double = 5.0 scala> Cpl(3.0, 4.0).img res17: Double = 4.0 scala> Cpl(3.0, 4.0).norm res18: Double = 5.0 scala> c / c res21: complex.Cpl = 1.0 + 0.0j
Testing in the REPL by compiling the script.
- Compile src/complex.scala to a jar file.
$ scalac src/complex.scala -d complex.jar $ file complex.jar complex.jar: Java archive data (JAR) $ unzip -l complex.jar Archive: complex.jar Length Date Time Name --------- ---------- ----- ---- 57 2017-09-26 02:22 META-INF/MANIFEST.MF 7057 2017-09-26 02:22 complex/Cpl.class 1726 2017-09-26 02:22 complex/Cpl$.class 951 2017-09-26 02:22 complex/Complex.class 840 2017-09-26 02:22 complex/Complex$.class 1411 2017-09-26 02:22 complex/Complex$DoubleToCpl.class --------- ------- 12042 6 files
- Load in the REPL
$ scala -cp complex.jar Welcome to Scala 2.12.3 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_144). Type in expressions for evaluation. Or try :help. import complex.Cpl import complex.Complex._ import scala.language.postfixOps scala> 6 + 8 * j res0: complex.Cpl = 6.0 + 8.0j scala> 6 + 8 * j res1: complex.Cpl = 6.0 + 8.0j scala> (6 + 8 * j) conj res2: complex.Cpl = 6.0 + -8.0j scala> (6 + 8 * j) norm res3: Double = 10.0
1.14.8 Abstract Classs
An abstract class is a class which cannot be instantiated, containing abstract methods (methods without implementation) and non-abstract methods .It is intended to be a base class or parent class for sub classes that will inherit it.
Example:
abstract class Shape { val name: String def getArea(): Double def getPerimiter(): Double def scaleDimensions(factor: Double): Unit } class Rectangle(xx: Double, yy: Double) extends Shape{ private var x = xx private var y = yy val name = "Rectangle" def getArea() = x * y def getPerimiter() = 2.0 * (x + y) def scaleDimensions(factor: Double) = { x = factor * x y = factor * y } def getX() = x def getY() = y def setX(nx: Double) = { x = nx } def setY(ny: Double) = { y = ny } } class Circle(r: Double) extends Shape{ private var radius = r private val pi = 3.1415 val name = "Circle" def getArea() = pi * radius * radius def getPerimiter() = 2.0 * pi * radius def scaleDimensions(factor: Double) = { radius = factor * radius } def getRadius() = radius def setRadius(r: Double) = { radius = r} } scala> val rec = new Rectangle(10.0, 20.0) rec: Rectangle = Rectangle@8f2e3e6 scala> rec.name res9: String = Rectangle scala> rec.getX() res5: Double = 10.0 scala> rec.getY() res6: Double = 20.0 scala> rec.getArea() res7: Double = 200.0 scala> rec.getPerimiter() res8: Double = 60.0 scala> rec.scaleDimensions(2.0) scala> rec.getX() res11: Double = 20.0 scala> rec.getArea() res12: Double = 800.0 scala> val circ = new Circle(10.0) circ: Circle = Circle@169d5567 scala> circ.name res13: String = Circle scala> circ.getArea() res14: Double = 314.15000000000003 scala> circ.getPerimiter() res15: Double = 62.830000000000005 scala> circ.getRadius() res17: Double = 10.0 scala> List(circ, rec) res18: List[Shape] = List(Circle@169d5567, Rectangle@8f2e3e6) scala> List(circ, rec).map(_.name) res19: List[String] = List(Circle, Rectangle) scala> List(circ, rec).map(_.getArea()) res20: List[Double] = List(314.15000000000003, 800.0) scala> List(circ, rec).map(_.getArea()).sum res21: Double = 1114.15 scala> List(circ, rec).map(_.getPerimiter()) res23: List[Double] = List(62.830000000000005, 120.0) scala> List(circ, rec).map(_.getPerimiter()).sum res24: Double = 182.83
1.14.9 Traits
Traits are similar to java interfaces. A class can inherit only one class, but can mix multiple traits and must implement the methods and fields of each trait.
Notes:
- A trait can have concrete and abstract methods.
- Traits must not have constructors.
- Unlike interfaces, traits can have implementation of methods.
trait Shape { def getName(): String def getArea(): Double } trait Location { var ox: Double var oy: Double def move(dx: Double, dy: Double): Unit def location(): (Double, Double) } class Rectangle(w: Double, h: Double) extends Shape with Location { var ox = 0.0 var oy = 0.0 def getName() = "rectangle" def getArea() = w * h def move(dx: Double, dy: Double) = { ox = ox + dx oy = oy + dy } def location() = (ox, oy) } class Circle(radius: Double) extends Shape with Location { var ox = 0.0 var oy = 0.0 def getName() = "circle" def getArea() = 3.1415 * radius * radius def move(dx: Double, dy: Double) = { ox = ox + dx oy = oy + dy } def location() = (ox, oy) } scala> val rec = new Rectangle(10.0, 20.0) rec: Rectangle = Rectangle@29526c05 scala> rec. getArea getName location move ox oy scala> rec.getArea() res0: Double = 200.0 scala> rec.getName() res1: String = rectangle scala> rec.location() res2: (Double, Double) = (0.0,0.0) scala> rec.move(20.0, 30.0) scala> rec.location() res4: (Double, Double) = (20.0,30.0) scala> rec.move(0.0, 10.0) scala> rec.location() res6: (Double, Double) = (20.0,40.0) scala> scala> val loc: Location = rec loc: Location = Rectangle@29526c05 scala> loc. location move ox ox_= oy oy_= scala> loc.location() res7: (Double, Double) = (20.0,40.0) scala> loc.ox res8: Double = 20.0 scala> loc.oy res9: Double = 40.0 scala> loc.getName() <console>:17: error: value getName is not a member of Location loc.getName() ^ scala> val circ = new Circle(10.0) circ: Circle = Circle@2ecdcfe3 scala> circ.getArea() res11: Double = 314.15000000000003 scala> circ.location() res12: (Double, Double) = (0.0,0.0) scala> circ.move(10.0, 30.0) scala> circ.location() res14: (Double, Double) = (10.0,30.0) scala> circ.move(10.0, 30.0) scala> circ.location() res16: (Double, Double) = (20.0,60.0) scala> List(circ, rec).map(_.getArea()) res17: List[Double] = List(314.15000000000003, 200.0) scala> List(circ, rec).map(_.location()) res18: List[(Double, Double)] = List((20.0,60.0), (20.0,40.0)) scala> List(circ, rec).map(_.getName()) res19: List[String] = List(circle, rectangle) def getArea(shape: Shape) = shape.getArea() scala> getArea(circ) res20: Double = 314.15000000000003 scala> getArea(rec) res21: Double = 200.0 def getDistance(loc: Location) = Math.sqrt(loc.ox * loc.ox + loc.oy * loc.oy) scala> getDistance(circ) res22: Double = 63.245553203367585 scala> getDistance(rec) res23: Double = 44.721359549995796 scala> List(circ, rec).map(getDistance) res24: List[Double] = List(63.245553203367585, 44.721359549995796)
1.15 Scala Type System
1.15.1 Subtyping annotation
As Scala is an object oriented language, it has subtyping, if the classes B and C are subclasses (aka derived classes) of the class A, then B and C are subtypes of A and any instance (object) of derived classes of A can be supplied where an object of type A is expected.
So the relations between classes B, C and A can be stated as:
- A <: B => It means that A is an subtype of B. It can also be said that the type A is upper bounded by B. This subtype relationship happens when a class A inherits a class B.
- A >: B => A is an supertype of B or that they type A is lower bounded by type B.
Example:
// Supertype class TCPProtocol{ def getName() = "generic TCP/IP protocol" } // Subtypes of TCPProtocol class FTP extends TCPProtocol{ override def getName() = "FTP Protocol" } class HTTP extends TCPProtocol{ override def getName() = "HTTP Protocol" def getMethods() = List("POST", "GET", "DELETE", "HEAD") } class Webdav extends HTTP{ override def getName() = "HTTP Protocol extension - Webdav" override def getMethods() = super.getMethods() ++ List("COPY", "LOCK", "MOVE") } // Sample client code: def identifyProtocol(prot: TCPProtocol) = { println("Network protocol is = " + prot.getName()) } // Sample client code with subtyping annotation // def identifyProtocolSub[P <: TCPProtocol](prot: P) = { println("[P] - Network protocol is = " + prot.getName()) }
Any subtype or subclass of TCPProtocol can be used where an instance of this class is expected such as in the function identifyProtocol which works with the protocols FTP, HTTP and Webdav. The same fact happens with the function identifyHTTPProtocol, it works with any subtype of HTTP such as HTTP and Webdav, but doesn't work with FTP or TCPProtocol. Generalizing what was stated, any subtype of a type can be used where the super type is expected, as reasult any subclass can be used where the parent class or interface is expected.
// Testing objects val tcp = new TCPProtocol() val http = new HTTP() val webdav = new Webdav() // The function identifyProtocol works with any subtype of TCPProtocol scala> identifyProtocol(tcp) Network protocol is = generic TCP/IP protocol scala> identifyProtocol(ftp) Network protocol is = FTP Protocol scala> identifyProtocol(http) Network protocol is = HTTP Protocol scala> identifyProtocol(webdav) Network protocol is = HTTP Protocol extension - Webdav scala> identifyProtocolSub(tcp) [P] - Network protocol is = generic TCP/IP protocol scala> identifyProtocolSub(http) [P] - Network protocol is = HTTP Protocol scala> identifyProtocolSub(webdav) [P] - Network protocol is = HTTP Protocol extension - Webdav scala> val protocolList = List(http, webdav, tcp) protocolList: List[TCPProtocol] = List(HTTP@dffcf1, Webdav@519e14f6, TCPProtocol@6d2f910b) scala> protocolList foreach identifyProtocolSub [P] - Network protocol is = HTTP Protocol [P] - Network protocol is = HTTP Protocol extension - Webdav [P] - Network protocol is = generic TCP/IP protocol // This function only accepts // instances of HTTP or instances of its subclasses def identifyHTTPProtocol(prot: HTTP) = { println("HTTP Protocol = " + prot.getName()) println("HTTP Methods = " + prot.getMethods()) } scala> identifyHTTPProtocol(webdav) HTTP Protocol = HTTP Protocol extension - Webdav HTTP Methods = List(POST, GET, DELETE, HEAD, COPY, LOCK, MOVE) scala> identifyHTTPProtocol(tcp) <console>:19: error: type mismatch; found : TCPProtocol required: HTTP identifyHTTPProtocol(tcp) ^
1.15.2 Variance
Variance is the relationship between subytpes of parametrized types or type constructos and subtypes of types parameters.
The relationship between the type constructor F[ ]
and its type
parameter T can be described as:
- Covariance
- Notation: F[+T]
- The type constructor F is covariant at parameter T if:
- A <: B implies that F[A] <: F[B]
- A <: B means that A is a subtype of B
- F[A] <: F[B] means that F[A] is a subtype of F[B]
- Contravariance
- Notation: F[-T]
- F[-A]
- The type constructor F is contravarint at parameter T if:
- A <: B (A is a subytpe of B) implies that F[B] <: F[A]
- Invariance => Invariant type constructor
- F[T]
- None of the relationship applies for the type constructor F at parameter T. So there is any type relationship between two types F[A] and F[B] when A <: B, A is an subtype of B.
Note:
- F[A <: B] denotes that the type constructor F can take any type
parameter (or class) wich is a subtype of B. It doesn't have the
same meaning.
- case class F[T <: Shape](t: T) … The parameter A of this the tyep constructor F can take any argument t wich is subtype of Shape, it can be a derived trait, derived class or case object.
- F[+T] means that that if an class A is a subtype of class B, then the
type F[A] will be a subtype of F[B].
- case class F[+T](t: T)
Example:
Sample experimentation classes:
trait Vehicle{ def getName(): String def getType(): String def getID(): Int override def toString() = s"Vehicle type = ${getName()} / name = ${getName()}" } class Car(name: String) extends Vehicle{ def getName() = name def getType() = "car" def getID() = 20 } class OilTanker(name: String) extends Vehicle{ def getName() = name def getType() = "Oil Tanker" def getID() = 251 } val carArray = Array(new Car("model1"), new Car("model2"), new Car("model2")) val tankerArray = Array(new OilTanker("FPSO X4AKT2"), new OilTanker("FPSO M4A372")) val car1 = new Car("Model X") val tanker1 = new OilTanker("Super oil carrier")
Experiments:
- Array (same as Java's array []) is an invariant type, thre is no type relationship between Array[Vehicle] and Array[Car]
// Java Array is invariant: The following code will not work // because Array[Car] is not a subtype of vehicle. // def countPortfolio(xs: Array[Vehicle]) = println("Number of items is equal to " + xs.length) scala> countPortfolio(carArray) <console>:14: error: type mismatch; found : Array[Car] required: Array[Vehicle] Note: Car <: Vehicle, but class Array is invariant in type T. You may wish to investigate a wildcard type such as `_ <: Vehicle`. (SLS 3.2.10) countPortfolio(carArray) ^ // It only works with exactly Array[Vehicle] scala> countPortfolio(carArray.asInstanceOf[Array[Vehicle]]) Number of items is equal to 3
- Scala's list is covariant as List[Car] is a subtype of List[Vehicle].
def countPortfolio2(xs: List[Vehicle]) = println("Number of items is equal to " + xs.length) scala> val vehicleList = List(new Car("m1"), new OilTanker("FPSO AXPF"), new Car("m3")) vehicleList: List[Vehicle] = List(Vehicle type = m1 / name = m1, ... ) scala> val carList = List(new Car("m1"), new Car("m3")) carList: List[Car] = List(Vehicle type = m1 / name = m1, Vehicle type = m3 / name = m3) scala> countPortfolio2(vehicleList) Number of items is equal to 3 scala> countPortfolio2(carList) Number of items is equal to 2
- The type constructor Container1 is invariant for type parameter A, therefore the function showContainer will only work with type showContainer.
// type Container1 is invariant at parameter A trait Container1[A]{ def get(): A } def showContainer1(cont: Container1[Vehicle]) = println("Container = " + cont.get()) scala> val cont1 = new Container1[Vehicle]{ def get() = car1 } cont3: Container1[Vehicle]{def get(): Car} = $anon$1@1cab1e3d scala> val cont2 = new Container1[Car]{ def get() = car1 } cont2: Container1[Car] = $anon$1@2b3e1279 scala> showContainer1(cont1) Container = Vehicle type = Model X / name = Model X scala> showContainer1(cont2) <console>:14: error: type mismatch; found : Container1[Car] required: Container1[Vehicle] Note: Car <: Vehicle, but trait Container1 is invariant in type A. You may wish to define A as +A instead. (SLS 4.5) showContainer1(cont2) ^
- The type constructor Container2 is covariant for type parameter A, as a result the function showContainer2 will work with any subtype of Vehicle.
// Type Container2 is variant at parameter A trait Container2[+A]{ def get(): A } def showContainer2(cont: Container2[Vehicle]) = println("Container = " + cont.get()) scala> val cont1b = new Container2[Vehicle]{ def get() = car1 } cont1b: Container2[Vehicle]{def get(): Car} = $anon$1@6bbaf0db scala> val cont2b = new Container2[Car]{ def get() = car1 } cont2b: Container2[Car] = $anon$1@52dd3c57 scala> showContainer2(cont1b) Container = Vehicle type = Model X / name = Model X scala> showContainer2(cont2b) Container = Vehicle type = Model X / name = Model X
Further Reading
- A practical tour of Scala's type system. http://chariotsolutions.com/wp-content/uploads/2016/04/HeatherMiller.pdf
- https://en.wikipedia.org/wiki/Subtyping
- Covariance and contravariance (computer science) - Wikipedia
- java - Covariance, Invariance and Contravariance explained in plain English? - Stack Overflow
- Subtype in Scala: what is "type X <: Y"? - Stack Overflow
- Armando Solar-Lezama. Type Classes and Subtyping
- Variance in scala.
1.16 Scala Implicit
1.16.1 Use Cases
Use Cases:
- Implict type conversion.
- Implicit conversions of Functions to Single-Method Interfaces
- Class Extension
- Type Classes
1.16.2 Implicit type conversion
1.16.2.1 Overview
Allows the compiler to implict convert a type A to another B where the type B is needed and a type A is supplied. Example: convert a integer 3 values tuple to Date object wherever is necessary a data object.
1.16.2.2 Converting Tuple to LocalDate
object DateTuple { import java.time.LocalDate implicit def tupleToDate(tpl: (Int, Int, Int)) = { val (y, m, d) = tpl LocalDate.of(y, m, d) } } //---------- Before importing DateTuple object / module ---------- // /// Java 8 Date API scala> java.time.LocalDate.of(2010, 10, 1) res83: java.time.LocalDate = 2010-10-01 scala> (2010, 10, 1): java.time.LocalDate <console>:12: error: type mismatch; found : (Int, Int, Int) required: java.time.LocalDate (2010, 10, 1): java.time.LocalDate ^ scala> (2010, 10, 1).getYear() <console>:12: error: value getYear is not a member of (Int, Int, Int) (2010, 10, 1).getYear() //---------- After importing DateTuple object -----------------------// scala> import DateTuple._ import DateTuple._ scala> (2010, 10, 1) res22: (Int, Int, Int) = (2010,10,1) // Compiler converts triple tuple to local date scala> (2010, 10, 1): java.time.LocalDate res6: java.time.LocalDate = 2010-10-01 // Compiler converts triple tuple to local date and invokes the method getYear. scala> (2010, 10, 1).getYear res10: Int = 2010 scala> (2010, 10, 1).getMonth res11: java.time.Month = OCTOBER scala> (2010, 10, 1).getDayOfMonth res12: Int = 1 scala> (2010, 10, 1).getDayOfYear res13: Int = 274 scala> List((2010, 10, 1), (1998, 10, 1), (2002, 4, 5)) map (_.getYear) res14: List[Int] = List(2010, 1998, 2002) scala> import java.time.LocalDate import java.time.LocalDate scala> val d1 = (2001, 10, 1): LocalDate d1: java.time.LocalDate = 2001-10-01 scala> val d2 : LocalDate = (2001, 10, 1) d2: java.time.LocalDate = 2001-10-01 scala> d1.getYear() res15: Int = 2001 scala> d2.getYear() res16: Int = 2001 scala> d1 == d2 res17: Boolean = true cala> def addDays(ndays: Int, d: java.time.LocalDate) = d.plusDays(ndays) addDays: (ndays: Int, d: java.time.LocalDate)java.time.LocalDate scala> addDays(10, java.time.LocalDate.of(2001, 10, 1)) res19: java.time.LocalDate = 2001-10-11 scala> addDays(10, (2001, 10, 1)) res20: java.time.LocalDate = 2001-10-11 scala> val t = (2001, 10, 1) t: (Int, Int, Int) = (2001,10,1) scala> addDays(10, t) res21: java.time.LocalDate = 2001-10-11
1.16.3 Implicit conversions of Functions to Single-Method Interfaces
1.16.3.1 Overview
Java has many single method-interfaces such as ActionListener, Runnable, Callable and etc which require a verbose instantiation of anonymous classes of those interfaces.
Scala implicit type conversion can make the code shorter by eliminating the need for instatiation of single-method interface through implicit converting a function to an anonymous class which implementing this single-method interface. For instance, it allows a function be passed as an argument to a method that requires an ActionListener or a Runnable interface.
As can be seen in the codes below all single-methods interfaces could be replaced by functions.
Runnable Interface:
interface Runnable{ public void run(); }
ActionListener
interface ActionListener{ public void actionPerformed(ActionEvent e); }
1.16.3.2 Converting Functions to Java Swing Event Listener
In the code below, the object (module) FunctionToListener provides functions that converts click event handler functions to ActionListener required by the JButton method addActionListener.
object FunctionToListener{ import java.awt.event.{ActionEvent, ActionListener} implicit def funToActionListener1(handler: ActionEvent => Unit) = { new ActionListener(){ def actionPerformed(evt: ActionEvent) = handler(evt) } } implicit def funToActionListener2(handler: () => Unit) = { new ActionListener(){ def actionPerformed(evt: ActionEvent) = handler() } } implicit def funToActionListener3(handler: => Unit) = { new ActionListener(){ def actionPerformed(evt: ActionEvent) = handler } } } //------- End of object FunctionToListener ------- // import javax.swing.{JFrame, JButton} import java.awt.event.{ActionEvent, ActionListener} val btn = new JButton("Click me") val frame = new JFrame("Hello world") frame.setSize(400, 500) frame.setLayout(new java.awt.FlowLayout()) frame.add(btn) frame.setVisible(true) // -------- Before importing FunctionToListener --------- // scala> btn.addActionListener _ res6: java.awt.event.ActionListener => Unit = $$Lambda$1404/1838333871@a23b96b // Error! scala> btn.addActionListener(() => println("I was clicked")) <console>:14: error: type mismatch; found : () => Unit required: java.awt.event.ActionListener btn.addActionListener(() => println("I was clicked")) // Error! scala> btn.addActionListener{ println("I was clicked")} <console>:15: error: type mismatch; found : Unit required: java.awt.event.ActionListener btn.addActionListener{ println("I was clicked")} // -------- After importing FunctionToListener --------- // import FunctionToListener._ btn.addActionListener(() => println("Handler1: I was clicked")) btn.addActionListener{ println("Handler2: I was clicked") } btn.addActionListener{ (evt: ActionEvent) => println("Handler3: I was clicked ") } // User clicks at the button ... scala> Handler3: I was clicked Handler2: I was clicked Handler1: I was clicked Handler3: I was clicked Handler2: I was clicked Handler1: I was clicked Handler3: I was clicked Handler2: I was clicked ... ... ... ... scala> val hnd: ActionListener = println("Hello Java") hnd: java.awt.event.ActionListener = FunctionToListener$$anon$3@28dd038f scala> val hnd2: ActionListener = () => println("Hello Java") hnd2: java.awt.event.ActionListener = FunctionToListener$$anon$2@4ca6f587
1.16.3.3 Converting code block to Runnable
object BlockToRunnable { /** Converts a code block to a Runnable instance */ implicit def funToRunnable(action: => Unit) = { new Runnable(){ def run() = action } } } //--------- Before Import the Helper object -------------- // // val th = new Thread( while(true){ println("I will run every 1 second") Thread.sleep(1000) }) scala> val th = new Thread( | while(true){ | println("I will run every 1 second") | Thread.sleep(1000) | }) <console>:11: error: overloaded method constructor Thread with alternatives: (x$1: String)Thread <and> (x$1: Runnable)Thread cannot be applied to (Unit) val th = new Thread( //--------- After Import the Helper object -------------- // // scala> import BlockToRunnable._ import BlockToRunnable._ // Now it works!! scala> val th = new Thread( | while(true){ | println("I will run every 1 second") | Thread.sleep(1000) | }) th: Thread = Thread[Thread-4,5,main] scala> th.start() scala> I will run every 1 second I will run every 1 second I will run every 1 second I will run every 1 second I will run every 1 second I will run every 1 second I will run every 1 second ... ... ... ... // A code block can be cast to runnable val block : Runnable = { while(true){ println("I will run every 1 second") Thread.sleep(1000) } } scala> block.run _ res0: () => Unit = $$Lambda$1311/1798538641@277474fc scala> block.run() I will run every 1 second I will run every 1 second I will run every 1 second I will run every 1 second
1.16.4 Extension methods
1.16.4.1 Overview
It is useful to add new functionality to code defined in libraries and sources that cannot be modified in a type-safe way.
1.16.4.2 String class extension to parse Dates
The String class extension, DateStr defines the methods toDate, toDateMDY and toDateMDY that can parse date strings.
object DateParsers { // International - ISO 8601 standard Date Format val dateFormatYMD = new java.text.SimpleDateFormat("yyyy-mm-dd") // American Date Format val dateFormatMDY = new java.text.SimpleDateFormat("mm-dd-yyyy") val dateFormatDMY = new java.text.SimpleDateFormat("dd-mm-yyyy") implicit class DateStr(s: String){ def toDate() = dateFormatYMD.parse(s) def toDateMDY() = dateFormatMDY.parse(s) def toDateDMY() = dateFormatDMY.parse(s) } } scala> import DateParsers._ import DateParsers._ scala> "2015-10-01".toDate res58: java.util.Date = Thu Jan 01 00:10:00 BRT 2015 scala> "10-01-2015".toDateMDY res61: java.util.Date = Thu Jan 01 00:10:00 BRT 2015 scala> "2015-10-01".toDate == "10-01-2015".toDateMDY res62: Boolean = true scala> "01-10-2015".toDateDMY == "10-01-2015".toDateMDY res63: Boolean = true scala> List("01-01-1970", "02-03-2001", "10-10-2002").map(_.toDate) res66: List[java.util.Date] = List(Mon May 24 00:01:00 BRT 6, Fri Jun 24 00:03:00 BRT 7, Tue Jun 25 00:10:00 BRT 15) scala> List("01-01-1970", "02-03-2001", "10-10-2002").map(_.toDate).foreach(println) Mon May 24 00:01:00 BRT 6 Fri Jun 24 00:03:00 BRT 7 Tue Jun 25 00:10:00 BRT 15 scala> val s : DateStr = "2001-10-01" s: DateParsers.DateStr = DateParsers$DateStr@32d87fe1 scala> s.toDate // Type Tab to complete toDate toDateDMY toDateMDY scala> s.toDate res68: java.util.Date = Mon Jan 01 00:10:00 BRT 2001
1.16.4.3 Extension to print elements.
object PrintHelpers { implicit class Printable (x: Any) { def printn() = println(x) } implicit class PrintSeq[A](seq: Seq[A]){ def printAll() = seq foreach println } implicit class PrintableArray[A](xs: Array[A]){ def printAll() = xs foreach println } implicit class PrintableList[A](xs: List[A]){ def printAll() = xs foreach println } } scala> 100 printn() 100 scala> "hello world" printn() hello world scala> List(1, 2, 3, 5) printn() List(1, 2, 3, 5) scala> List(1, 2, 3, 4).printAll() 1 2 3 4 scala> Array(1, 2, 3, 4).printAll() 1 2 3 4 scala> val xs: Seq[Int] = List(1, 2, 4, 5) xs: Seq[Int] = List(1, 2, 4, 5) scala> xs.printAll() 1 2 4 5 scala> new java.io.File("/").listFiles().printAll() /home /var /bin /usr /root /Applications ... ...
1.16.4.4 Reverse function application
The implicit class Apply allows the compiler convert any Scala object to this class whenever the method ap is invoked. The class Apply can be used to peform reverse function application like the F# and OCaml operator (|>).
object ApplyHelper{ implicit class Apply[A](x: A){ def ap[B](f: A => B) = f(x) } } scala> import ApplyHelper._ import ApplyHelper._ scala> "hello world" ap println hello world scala> val s = "hello world" : Apply[String] s: ApplyHelper.Apply[String] = ApplyHelper$Apply@3fa2c8e7 scala> s.ap(println) hello world scala> s ap println hello world scala> 1000.0 ap Math.log10 res24: Double = 3.0 scala> 1000.0 ap Math.log10 ap Math.exp res26: Double = 20.085536923187668 scala> 1000.0 ap Math.log10 ap Math.exp ap Math.log res27: Double = 3.0 scala> 1000.0 ap Math.log10 ap Math.exp ap Math.log ap println 3.0 scala> new java.io.File("/") ap (_.listFiles) ap(_.foreach(println)) /home /var /bin /usr /root /Applications /proc /boot /dev ... .... ...
1.16.5 Implicit parameter passing - basic
Methods or functions with implicit parameters can be called passing an implicit value in the scope when the parameters marked as implicit are not provided.
@ def mulBy(x: Int)(implicit y: Int) = x * y defined function mulBy @ mulBy(10)(5) res1: Int = 50 @ mulBy(10)(2) res2: Int = 20
Before supplying an implicit value: It fails because there is no implicit value in the scope of type int.
@ mulBy(10) cmd3.sc:1: could not find implicit value for parameter y: Int val res3 = mulBy(10) ^ Compilation Failed
After supplying an implicit value: The implicit value a is passed to the function mulBy implicitly.
// The name of the implicita parameter doesn't matter. // The compiler looks for the implicit parameter of type int // at the current context. //----------------------------------------------------------- @ implicit val a = 10 a: Int = 10 // y = 10 @ mulBy(10) res4: Int = 100 // y = 10 @ mulBy(3) res5: Int = 30 // y = 2 @ mulBy(3)(2) res6: Int = 6 // The implicit parameter can be supplied only once. scala> implicit val x = 5 x: Int = 5 scala> mulBy(4) <console>:15: error: ambiguous implicit values: both value a of type => Int and value x of type => Int match expected type Int mulBy(4)
Implicit parameters and escope:
- Note: There can be only one implicit parameter of a single type.
// Restart experiment by exiting REPL and restarting again. // def mulBy(x: Int)(implicit y: Int) = x * y scala> mulBy(10) <console>:13: error: could not find implicit value for parameter y: Int mulBy(10) { println("mulBy(10) = " + mulBy(10)) println("mulBy(4) = " + mulBy(4)) } scala> { | println("mulBy(10) = " + mulBy(10)) | println("mulBy(4) = " + mulBy(4)) | } <console>:14: error: could not find implicit value for parameter y: Int println("mulBy(10) = " + mulBy(10)) ^ <console>:15: error: could not find implicit value for parameter y: Int println("mulBy(4) = " + mulBy(4)) // Supply implicit value at local scope { implicit val a = 5 println("mulBy(10) = " + mulBy(10)) println("mulBy(4) = " + mulBy(4)) } scala> { | implicit val a = 5 | println("mulBy(10) = " + mulBy(10)) | println("mulBy(4) = " + mulBy(4)) | } mulBy(10) = 50 mulBy(4) = 20 // Supply implicit value at local scope (y = 6) { implicit val k = 6 println("mulBy(10) = " + mulBy(10)) println("mulBy(4) = " + mulBy(4)) } scala> { | implicit val k = 6 | println("mulBy(10) = " + mulBy(10)) | println("mulBy(4) = " + mulBy(4)) | } mulBy(10) = 60 mulBy(4) = 24 def computeWithImplicit(k: Int) = { implicit val y = k println("mulBy(10) = " + mulBy(10)) println("mulBy(4) = " + mulBy(4)) } scala> computeWithImplicit(3) mulBy(10) = 30 mulBy(4) = 12 scala> computeWithImplicit(4) mulBy(10) = 40 mulBy(4) = 16
Multiple Implicit Values:
def tellYourName(implicit name: String, surname: String) = scala> tellNameAndID(200, "Somebody else") Name of user = Somebody else, user ID = 200 scala> tellNameAndID <console>:13: error: could not find implicit value for parameter userID: Int tellNameAndID { implicit val a = 200 implicit val x = "John Ghost" tellNameAndID } scala> { | implicit val a = 200 | implicit val x = "John Ghost" | tellNameAndID | } Name of user = John Ghost, user ID = 200 { implicit val a = 25 implicit val x = "Dummy Test User" tellNameAndID } scala> { | implicit val a = 25 | implicit val x = "Dummy Test User" | tellNameAndID | } Name of user = Dummy Test User, user ID = 25 // There can be only one implicit value of a given type. // More than one int implicit value will generate an compile // error. { implicit val a = 25 implicit val z = 10 implicit val x = "Dummy Test User" tellNameAndID } scala> { | implicit val a = 25 | implicit val z = 10 | implicit val x = "Dummy Test User" | tellNameAndID | } <console>:17: error: ambiguous implicit values: both value z of type Int and value a of type Int match expected type Int tellNameAndID
1.17 OO - Design Patterns
1.17.1 Overview
GOF - Gang of Four Design Patterns or Design Patterns: Elements of Reusable Object-Oriented Software (Addison-Wesley Publishing Company, 1995)
Design Patterns
- Creational Pattern - Patterns concerned with object
instatiation/creation.
- Factory pattern
- Simple Factory
- Factory Method
- Factory pattern
- Behavioral Patterns - Patterns that focus on communication
between objects.
- Strategy Pattern - Allows switching algorithm/strategy at run-time.
- Observer / Publisher-Subscriber
- Structural - Patterns fucusing on objects composition to extend its functionality.
Creational Patterns
Factory Pattern
- Intent: Instantiate classes with a common parent class or interface without specifying a concrete class.
Singleton
- Intent: Ensure that a class with only one instance.
Structural Patterns
Facade Pattern
- Provide a unified and simplified interface to a complex subsystem.
Behavioral Patterns
Strategy Pattern
- Intent: Encapsulate algorithms with objects and switch them at run-time.
Observer Pattern
- Intent: Define one-to-many dependency between objects.
- Known uses: Event driven-systems such as GUIs such as Gtk, QT and Java Swing events or Model in model view controller pattern.
Iterator Pattern
- Intent: Access elements of a container without exposing its representation.
- Known uses: Java Iterators, C++ STL iterators.
1.17.2 Creational Design Patterns
1.17.2.1 Singleton Design Pattern
Singleton is a creational design pattern where there is only a single instance of a class and the client code is not allowed to create more instances.
Example: As the singleton pattern is already a language feature of Scala, it will be shown first the Java implementation of the pattern and then the Scala implementation.
File: Singleton.java
public class Singleton{ private static Singleton _instance = new Singleton(); private String _name = "unnamed"; public void setName(String name){ _name = name; } public void service1(){ System.out.println("Hello world user: " + _name); System.out.println("Scala is much better than Java!"); } public static Singleton getInstance(){ return _instance; } }
- Compiling and running:
# Compile Singleton.java to generate Singleton.class # $ javac Singleton.java # Run Scala REPL and play with compiled Java code. # $ scala Welcome to Scala 2.12.4 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_162). Type in expressions for evaluation. Or try :help. scala> val s = new Singleton() <console>:11: error: trait Singleton is abstract; cannot be instantiated val s = new Singleton() ^ scala> val s = Singleton.getInstance() s: Singleton = Singleton@517a2b0 scala> s.service1() Hello world user: unnamed Scala is much better than Java! scala> s.setName("Somebody else") scala> s.service1() Hello world user: Somebody else Scala is much better than Java! scala> val s2 = Singleton.getInstance() s2: Singleton = Singleton@517a2b0 scala> s2.service1() Hello world user: Somebody else Scala is much better than Java! # Check if s2 and s have the same reference, point to the same # memory location. scala> s2 == s res3: Boolean = true scala> s2.service1() Hello world user: Somebody else Scala is much better than Java!
Scala way: In Scala, there are no static methods or static classes (classes only with static methods), however they can be replaced by the singleton created with the "object" keyword.
object SingletonScala{ // It could be: private var _name = "unnamed" private var _name: String = "unnamed" def setName(name: String) = _name = name def service1() = { println("Hello world user: " + _name) System.out.println("Scala is much better than Java!") } }
Testing in REPL:
scala> SingletonScala.service1() Hello world user: unnamed Scala is much better than Java! scala> SingletonScala.setName("Thor") scala> val ss1 = SingletonScala ss1: SingletonScala.type = SingletonScala$@12811f95 scala> ss1.service1() Hello world user: Thor Scala is much better than Java! // Reference equality, aka identity equality scala> ss1 == SingletonScala res9: Boolean = true
The Java code mentioned before could be emulated in this way:
class Singleton2 private { private var _name: String = "unnamed" def setName(name: String) = _name = name def service1() = { println("Hello world user: " + _name) System.out.println("Scala is much better than Java!") } } object Singleton2{ private val instance = new Singleton2() def getInstance() = instance }
Testing:
// Copy class Singleton2 and companion object Singleton2 paste them together // with command (:paste) in Scala REPL. // :paste scala> val ssa = new Singleton2() <console>:13: error: constructor Singleton2 in class Singleton2 cannot be accessed in object $iw val ssa = new Singleton2() ^ scala> val ssa = Singleton2.getInstance() ssa: Singleton2 = Singleton2@71d6e503 scala> ssa.service1() Hello world user: unnamed Scala is much better than Java! scala> ssa.setName("Someone") scala> ssa.service1() Hello world user: Someone Scala is much better than Java! scala> val ssb = Singleton2.getInstance() ssb: Singleton2 = Singleton2@71d6e503 scala> ssb.service1() Hello world user: Someone Scala is much better than Java!
1.17.2.2 Simple factory pattern
Defined by GOF as: "Define an interface for creating an object, but let subclasses decide which class to instantiate. Factory Method lets a class defer instantiation to subclasses."
The factory pattern is used to create instances of different subclasses of a class. A factory class instantiates a subclass based on the input passed to the factory.
Parts:
- Product - Parent class which subclasses will be instantiated by the factory.
- Factory - Class that instantiate product subclasses.
The class ComputerFactory is used to instantiate classes that implements the trait (interface) based on the input passed to the factory.
Note: This pattern is not extensible since, the class ComputerFactory needs to be modified for every new Computer subclass.
trait Computer { def getManufacturer(): String def getID(): Int def clockGHZ: Int override def toString() = { val m = getManufacturer() val id = getID() s"{ Manufacturer = $m id = $id clock = $clockGHZ }" } } class ComputerA extends Computer { def getManufacturer() = "SunJitsu" def getID() = 100 def clockGHZ = 4 } class ComputerB extends Computer { def getManufacturer() = "some manufacturer" def getID() = 300 def clockGHZ = 100 } class ComputerC extends Computer { def getManufacturer() = "unknown manufacturer" def getID() = 350 def clockGHZ = 4 } class ComputerFactory() { def getComputer(comp: String) = comp match { case "compA" => new ComputerA() case "compB" => new ComputerB() case "compC" => new ComputerC() case _ => error("Error: This type of computer doesn't exist") } } scala> val factory = new ComputerFactory() factory: ComputerFactory = ComputerFactory@6acb0d0d scala> val compA = factory.getComputer("compA") compA: Computer = { Manufacturer = SunJitsu id = 100 clock = 4 } scala> compA.getManufacturer() res29: String = SunJitsu scala> compA.getID() res30: Int = 100 scala> scala> val compB = factory.getComputer("compB") compB: Computer = { Manufacturer = some manufacturer id = 300 clock = 100 } scala> val compC = factory.getComputer("compC") compC: Computer = { Manufacturer = unknown manufacturer id = 350 clok = 4 } scala> factory.getComputer("comp") java.lang.RuntimeException: Error: This type of computer doesn't exist at scala.sys.package$.error(package.scala:27) at scala.Predef$.error(Predef.scala:144) at ComputerFactory.getComputer(<console>:20) ... 32 elided scala> List(compA, compB, compC) res34: List[Computer] = List({ Manufacturer = SunJitsu id = 100 clok = 4 }, { Manufacturer = some manufacturer id = 300 clok = 100 }, { Manufacturer = unknown manufacturer id = 350 clok = 4 }) scala> List(compA, compB, compC).foreach(println) { Manufacturer = SunJitsu id = 100 clok = 4 } { Manufacturer = some manufacturer id = 300 clok = 100 } { Manufacturer = unknown manufacturer id = 350 clok = 4 } scala> List(compA, compB, compC).map(_.getID()) res42: List[Int] = List(100, 300, 350)
Functional Simplification: The factory class can replaces by a function that instantiates the product sub classes.
def getComputer(comp: String) = comp match { case "compA" => new ComputerA() case "compB" => new ComputerB() case "compC" => new ComputerC() case _ => error("Error: This type of computer doesn't exist") } scala> getComputer _ res46: String => Computer = <function1> scala> getComputer("compA") res47: Computer = { Manufacturer = SunJitsu id = 100 clock = 4 }
References:
- Exploring the Factory Design Pattern https://msdn.microsoft.com/en-us/library/ee817667.aspx
- Design Pattern - Factory Pattern https://www.tutorialspoint.com/design_pattern/factory_pattern.htm
- Factory Design Pattern in Java http://howtodoinjava.com/design-patterns/creational/implementing-factory-design-pattern-in-java/
- Why should I use a factory class instead of direct object construction? - https://softwareengineering.stackexchange.com/questions/253254/why-should-i-use-a-factory-class-instead-of-direct-object-construction
- Factory Pattern http://www.oodesign.com/factory-pattern.html
1.17.2.3 Factory pattern with factory method
This variation allows multiple subclasses be added to the factory.
trait DatabaseDriver { def dbname: String def connect(Uri: String): Unit def createDriver(): DatabaseDriver } class DbSqlite extends DatabaseDriver { val dbname = "sqlite" def connect(uri: String) = { println("Connect to SQlite database: " + uri) } def createDriver() = new DbSqlite() } class DbPostgres extends DatabaseDriver { val dbname = "postgres" def connect(uri: String) = { println("Connect to Postgres database: " + uri) } def createDriver() = new DbPostgres() } class DbMysql extends DatabaseDriver { val dbname = "mysql" def connect(uri: String) = { println("Connect to Mysql database: " + uri) } def createDriver() = new DbMysql() } class DabaseFactory { val dbMap: scala.collection.mutable.Map[String, DatabaseDriver] = scala.collection.mutable.Map() def register(driver: DatabaseDriver) = { dbMap += (driver.dbname -> driver) } def getDatabase(dbname: String) = dbMap(dbname).createDriver() } val dbFactory = new DabaseFactory() /// Register database classe. dbFactory.register(new DbMysql()) dbFactory.register(new DbSqlite()) dbFactory.register(new DbPostgres()) scala> val sqliteDriver = dbFactory.getDatabase("sqlite") sqliteDriver: DatabaseDriver = DbSqlite@41a9920d scala> val pgsqlDriver = dbFactory.getDatabase("postgres") pgsqlDriver: DatabaseDriver = DbPostgres@1a4b27e1 scala> val mysqlDriver = dbFactory.getDatabase("mysql") mysqlDriver: DatabaseDriver = DbMysql@4f5a3111 scala> scala> sqliteDriver.dbname res61: String = sqlite scala> sqliteDriver.connect("file://somedb.sqlite") Connect to SQlite database: file://somedb.sqlite scala>
Reference:
- Factory Pattern http://www.oodesign.com/factory-pattern.html
- what's the difference between a simple factory, a factory method design pattern, and an abstract factory? https://www.linkedin.com/pulse/20140901184348-90925576-what-s-the-difference-between-a-simple-factory-a-factory-method-design-pattern-and-an-abstract-factory
- Simple Factory Vs Factory Method Vs Abstract Factory by Example https://vivekcek.wordpress.com/2013/03/17/simple-factory-vs-factory-method-vs-abstract-factory-by-example/
1.17.2.4 Static Factory Method
The static factory method is a design pattern that uses private constructor and static methods to create instances of the class, instead of public constructors and constructor overload. The benefits of this methods are:
- Unlike constructors, static methods can have meaningful names.
- It allows the private constructor to be changed without breaking client code.
- Multiple constructors cannot have have different type signature. Static methods, doesn't have those limitations.
- Allows multiple ways to instantiate a class.
Note: It should not be confused with GOF's factory design pattern.
Example 1:
The class Interval uses milliseconds as its internal data representation and can be instantiated as an interval of seconds, minutes, hours or days.
As Scala doesn't have static methods, this pattern is implemented using an companion-object which is a singleton with the same name of the class. The companion-object Interval can access any private method, field or constructor of the class Interval.
// Constructor is private and it is only accessible from // companion object class Interval private (timeMs: Double){ def getSeconds() = timeMs / 1000.0 def getMinutes() = timeMs / 60000.0 def getHours() = timeMs / 3600000.0 def getDays() = timeMs / 86400000.0 } // The companion-object can access the class' // with same name private methods and data. // object Interval{ def ofSeconds(time: Int) = new Interval(time * 1000) def ofMinutes(time: Int) = new Interval(time * 60000) def ofHours(time: Int) = new Interval(time * 3600000) def ofDays(time: Int) = new Interval(time * 86400000) }
Testing in the REPL:
scala> val i0 = new Interval(1000) <console>:13: error: constructor Interval in class Interval cannot be accessed in object $iw val i0 = new Interval(1000) ^ scala> val i1 = Interval.ofSeconds(600) i1: Interval = Interval@1da074ef scala> i1.getSeconds() res10: Double = 600.0 scala> i1.getMinutes() res11: Double = 10.0 scala> i1.getHours() res12: Double = 0.16666666666666666 scala> val i2 = Interval.ofHours(3) i2: Interval = Interval@23d5ada9 scala> i2.getSeconds() res13: Double = 10800.0 scala> i2.getMinutes() res14: Double = 180.0 scala> i2.getHours() res15: Double = 3.0 scala> i2.getDays() res16: Double = 0.125
Example 2:
class Coordinate private (x: Double, y: Double) { import Math.{sqrt, atan2, PI} def getX() = x def getY() = y def getRadius() = sqrt(x * x + y * y) def getAngle() = atan2(y, x) * 180.0 / PI // Get rectangular coordinates representation def getRec() = (x, y) // Get polar coordinates representation def getPol() = (getRadius(), getAngle()) } object Coordinate{ import Math.{sqrt, atan2, PI, sin, cos} // Default "constructor" def apply(x: Double, y: Double) = new Coordinate(x, y) // Create a coordinate in rectangular coordinate def ofRec(x: Double, y: Double) = new Coordinate(x, y) // Create a coordinate in polar coordinates def ofPol(radius: Double, angle: Double) = { // Angle "a" in Radians val a = angle / 180.0 * PI new Coordinate(radius * cos(a), radius * sin(a)) } }
Repl test:
scala> val c1 = new Coordinate(3.0, 5.0) <console>:13: error: constructor Coordinate in class Coordinate cannot be accessed in object $iw val c1 = new Coordinate(3.0, 5.0) ^ // Apply method scala> val c1 = Coordinate(3.0, 5.0) c1: Coordinate = Coordinate@a9f946f scala> val c1 = Coordinate(5.0, 5.0) c1: Coordinate = Coordinate@2b86d46b scala> val c1 = Coordinate(0.0, 5.0) c1: Coordinate = Coordinate@de96d10 scala> c1.getX() res19: Double = 0.0 scala> c1.getY() res20: Double = 5.0 scala> c1.getPol() res21: (Double, Double) = (5.0,90.0) scala> val c2 = Coordinate.apply(0.0, 5.0) c2: Coordinate = Coordinate@17d2221f scala> c2.getRec() res23: (Double, Double) = (0.0,5.0) scala> c2.getPol() res24: (Double, Double) = (5.0,90.0) // Create coordinate given in Polar Coordinates scala> val c3 = Coordinate.ofPol(10.0, 45.0) c3: Coordinate = Coordinate@1f53e98 scala> c3.getRec() res25: (Double, Double) = (7.0710678118654755,7.071067811865475) scala> c2.getPol() res27: (Double, Double) = (5.0,90.0)
Futher Reading:
- 5 Difference between Constructor and Static Factory method in Java- Pros and Cons - http://javarevisited.blogspot.com.br/2017/02/5-difference-between-constructor-and-factory-method-in-java.html
- Replace Constructor with Factory Method - https://refactoring.guru/replace-constructor-with-factory-method
- Java Static Factory Method - https://www.slideshare.net/mysky14/java-static-factory-methods
1.17.3 Behavioral Patterns
1.17.3.1 Strategy Pattern
- Overview
Strategy pattern allows changing the algorithm (strategy) at run-time. The strategy object encapsulates the algorithm, in other words, implements the strategy to be executed. The context object switches its behavior by switching the context object.
Intent: Define a family of algorithms, encapsulate each one, and make them interchangeable.
This pattern has three main parts:
- Strategy: the interface that defines how the algorithm will be called.
- Concrete Strategy: the implementation of the strategy (algorithm).
- Context: the object holding the Concrete Strategy.
References:
- Introduction to Design Patterns https://www.intertech.com/Downloads/Whitepapers/Intertech-Design-Patterns.pdf
- Design Patterns - Strategy Pattern https://www.tutorialspoint.com/design_pattern/strategy_pattern.htm
- CS 635 Advanced Object-Oriented Design & Programming - http://www.eli.sdsu.edu/courses/spring01/cs635/notes/strategy/strategy.html
- Strategy pattern - https://en.wikipedia.org/wiki/Strategy_pattern
- Example in OO way
/// Interface for the Strategy - Algorithm trait Operation { def run(x: Double, y: Double): Double } /// -------- Concrete strategies ------- /// class AddOp() extends Operation { def run(x: Double, y: Double) = x + y } class SubOp extends Operation { def run(x: Double, y: Double) = x - y } class MulOp extends Operation { def run(x: Double, y: Double) = x * y } //----- Context - Class that selects the concrete strategy -- // class Context{ private var strategy: Operation = null def setStrategy(strategyS: Operation) = { strategy = strategyS } def runStrategy(x: Double, y: Double) = { strategy.run(x, y) } } // ------- Running -------------- // scala> val ctx = new Context() ctx: Context = Context@2f18e88b scala> ctx.setStrategy(new AddOp()) scala> ctx.runStrategy(10.0, 5.0) res69: Double = 15.0 scala> ctx.setStrategy(new MulOp()) scala> ctx.runStrategy(63.0, 4.0) res71: Double = 252.0 scala> ctx.setStrategy(new SubOp()) scala> ctx.runStrategy(63.0, 4.0) res73: Double = 59.0 scala>
- Example in FP way
In functional programming each algorithm is just a function. The context object can be replaced by a higher order function. By the changing the function argument of the context (higher order function) its changes the behavior.
Variation 1: Each concrete strategy can become just a binary function.
type Operation = (Double, Double) => Double val addOp: Operation = (x: Double, y: Double) => x + y val subOp: Operation = (x: Double, y: Double) => x - y val mulOp: Operation = (x: Double, y: Double) => x * y class Context{ private var strategy: Operation = null def setStrategy(strategyS: Operation) = { strategy = strategyS } def runStrategy(x: Double, y: Double) = { strategy(x, y) } } scala> val ctx = new Context() ctx: Context = Context@26275bef scala> ctx.setStrategy(addOp) scala> ctx.runStrategy(10, 5) res1: Double = 15.0 scala> ctx.setStrategy(mulOp) scala> ctx.runStrategy(10, 5) res3: Double = 50.0 scala> ctx.setStrategy(subOp) scala> ctx.runStrategy(10, 5) res5: Double = 5.0 scala>
Variation 2: Only with functions:
- The function runStrategy switches algorithms at run-time. The algorithm is just a function passed as argument.
type Operation = (Double, Double) => Double val addOp: Operation = (x: Double, y: Double) => x + y val subOp: Operation = (x: Double, y: Double) => x - y val mulOp: Operation = (x: Double, y: Double) => x * y def runStrategy(strategy: Operation) = (x: Double, y: Double) => strategy(x, y) scala> def runStrategy(strategy: Operation) = (x: Double, y: Double) => strategy(x, y) runStrategy: (strategy: Operation)(Double, Double) => Double scala> runStrategy _ res80: Operation => ((Double, Double) => Double) = <function1> scala> runStrategy(addOp) res75: (Double, Double) => Double = <function2> scala> runStrategy(addOp)(3, 4) res76: Double = 7.0 scala> runStrategy(mulOp)(5, 10) res77: Double = 50.0 scala> runStrategy(subOp)(5, 10) res78: Double = -5.0
Variation 3: An alternative way to avoid defining many classes and using an mixed OOP and FP approach is to create a function that returns an anonymous class implementing the interface/trait operation. This method is useful when it is not possible to modify the code of the Operation trait/interface and class Context. This approach is also helpful in the case that many external codes, libraries and programs could be disrupted if the mentioned codes are modified.
/// Interface for the Strategy - Algorithm trait Operation { def run(x: Double, y: Double): Double } //----- Context - Class that selects the concrete strategy -- // class Context{ private var strategy: Operation = null def setStrategy(strategyS: Operation) = { strategy = strategyS } def runStrategy(x: Double, y: Double) = { strategy.run(x, y) } } // Converts a function to the interface "Operation" def makeStrategy(operation: (Double, Double) => Double) = new Operation{ def run(x: Double, y: Double) = operation(x, y) } scala> val addOp = makeStrategy{(x, y) => x + y} addOp: Operation = $anon$1@6f0a4e30 scala> val subOp = makeStrategy{(x, y) => x - y} subOp: Operation = $anon$1@5e020dd1 scala> val mulOp = makeStrategy{(x, y) => x * y} mulOp: Operation = $anon$1@2a0ce342 scala> val ctx = new Context() ctx: Context = Context@1e160a9e // Algorithm changed at run-time scala> ctx.setStrategy(addOp) scala> ctx.runStrategy(3.5, 4.5) res1: Double = 8.0 scala> ctx.setStrategy(mulOp) scala> ctx.runStrategy(10, 3.5) res3: Double = 35.0
Alternative 4: Languages like C++ and C# don't support anonymous classes like Java and Scala, so an alternative approach in this case is to create a classe implemeting the interface Operation that takes a lambda function as constructor argument and delegates the execution of the method defined in the interface to the lambda.
/// Interface for the Strategy - Algorithm trait Operation { def run(x: Double, y: Double): Double } //----- Context - Class that selects the concrete strategy -- // class Context{ private var strategy: Operation = null def setStrategy(strategyS: Operation) = { strategy = strategyS } def runStrategy(x: Double, y: Double) = { strategy.run(x, y) } } class FunctionToStrategy(operation: (Double, Double) => Double) extends Operation { def run(x: Double, y: Double) = operation(x, y) } scala> val addOp = new FunctionToStrategy((x, y) => x + y) addOp: FunctionToStrategy = FunctionToStrategy@43874120 scala> val subOp = new FunctionToStrategy((x, y) => x - y) subOp: FunctionToStrategy = FunctionToStrategy@282506e1 scala> val mulOp = new FunctionToStrategy((x, y) => x * y) mulOpt: FunctionToStrategy = FunctionToStrategy@7dbcbc7b scala> val ctx = new Context() ctx: Context = Context@7f53c24c // Algorithm changed at run-time scala> ctx.setStrategy(addOp) scala> ctx.runStrategy(3.5, 4.5) res1: Double = 8.0 scala> ctx.setStrategy(mulOp) scala> ctx.runStrategy(10, 3.5) res3: Double = 35.0
The alternative 4 could be also implemented in C++11:
- File: strategyPattern.cpp
#include <iostream> #include <functional> using std::string; using std::function; // Type synonym to function // type FOperation = (double, double) => double using FOperation = function<double (double, double)>; // Class with only with pure virtual methods is equivalent to an interface. class IOperation{ public: virtual string getName() = 0; virtual double run(double x, double y) = 0; }; class Context{ private: IOperation* _strategy = nullptr; public: void setStrategy(IOperation* strategy){ _strategy = strategy; } double runStrategy(double x, double y){ double result = _strategy->run(x, y); std::cout << "Running strategy = " << _strategy->getName() << "( x = " << x << ", y = " << y << ") = " << result << std::endl; return _strategy->run(x, y); } }; // Turns a lambda function into a "Strategy" object class FunctionToStrategy: public IOperation{ private: string _name; FOperation _oper; public: FunctionToStrategy(string name, FOperation oper): _name(name), _oper(oper){} string getName(){ return _name; } double run(double x, double y){ return _oper(x, y); } }; int main(int argc, char** argv, char **environ){ std::cout << "Testing mixed FP and OOP strategy pattern in C++" << std::endl; FunctionToStrategy strategyAdd = FunctionToStrategy( "add", [](double x, double y){ return x + y; } ); FunctionToStrategy strategyMul = FunctionToStrategy( "mul", [](double x, double y){ return x * y; } ); Context ctx; std::cout << std::endl; std::cout << "Testing operation add" << std::endl; std::cout << "---------------------" << std::endl; ctx.setStrategy(&strategyAdd); double res0 = ctx.runStrategy(10.0, 20.0); std::cout << "Add result = " << res0 << std::endl; ctx.runStrategy(15.0, 20.0); std::cout << std::endl; std::cout << "Testing operation mul" << std::endl; std::cout << "---------------------" << std::endl; ctx.setStrategy(&strategyMul); double res1 = ctx.runStrategy(10.0, 20.0); std::cout << "Mul result = " << res1 << std::endl; ctx.runStrategy(3, 5); // return zero status code return EXIT_SUCCESS; }
Compiling and Running:
$ clang++ strategyPattern.cpp -std=c++11 -o strategyPattern.bin && ./strategyPattern.bin Testing mixed FP and OOP strategy pattern in C++ Testing operation add --------------------- Running strategy = add( x = 10, y = 20) = 30 Add result = 30 Running strategy = add( x = 15, y = 20) = 35 Testing operation mul --------------------- Running strategy = mul( x = 10, y = 20) = 200 Mul result = 200 Running strategy = mul( x = 3, y = 5) = 15
1.17.3.2 Template Method
The templated method design patterns is a behavioral design pattern which provides an algorithm, also known as template which defines some steps and defers the implementation of some steps to subclasses.
It is stated by GOF as: "Defines the skeleton of an algorithm in a method, deferring some steps to subclasses. Template Method lets subclasses redefine certain steps of an algorithm without changing the algorithms structure."
- Intent: Create an algorithm template that allows redefine some steps without changing its structure.
The parent abstract class has four different types of methods:
- Concrete methods: Methods implemented in the abstract class.
- Abstract methods: Methods without implementation that must be implemented by subclasses.
- Hook methods: Methods with default implementation that can be overriden by subclasses.
- Template methods: Method that calls concrete methods, abstract methods or hook methods.
Example: The method setepFn is a hook method with default implementation and the summation is a template method.
trait IntervalSummation { // Summation method with default implementation def stepFn(a: Int) = a // Summation of all numbers in the interval def summation(lower: Int, upper: Int) = { var result = 0 for (a <- lower to upper) { result = result + stepFn(a) } result } } scala> class IntervalIdentity extends IntervalSummation defined class IntervalIdentity scala> val intervId = new IntervalIdentity() intervId: IntervalIdentity = IntervalIdentity@59ede173 scala> intervId.summation(0, 10) res14: Int = 55 scala> intervId.summation(0, 100) res15: Int = 5050 class IntervalSquare extends IntervalSummation { override def stepFn(a: Int) = a * a } scala> intervSquare.summation(0, 10) res16: Int = 385 scala> intervSquare.summation(0, 100) res17: Int = 338350 class IntervalCube extends IntervalSummation { override def stepFn(a: Int) = a * a * a } scala> intervCube.summation(0, 10) res18: Int = 3025 scala> intervCube.summation(0, 100) res19: Int = 25502500
References:
- The Template Method Pattern - http://ima.udg.edu/~sellares/EINF-ES1/TemplateMethodToni.pdf
- Template Method Design Pattern - https://www.slideshare.net/srikanthps/template-method-design-pattern
- Template Method Design Pattern in Java - https://stacktips.com/tutorials/design-patterns/template-method-design-pattern-in-java
- Design Patterns - Template Pattern https://www.tutorialspoint.com/design_pattern/template_pattern.htm
- Template Method Pattern Tutorial with Java Examples https://dzone.com/articles/design-patterns-template-method
1.17.3.3 Iterator Pattern
- Overview
The iterator pattern allows tranversing a collection or container without expose its internal structure and provides a uniform interface interface to tranverse different types of collections.
- Stated by GOF as "Provide a way to access the elements of an aggregate object sequentially without exposing its underlying representation."
Intent: Provide a way to transverse a collection or container sequentially not exposing its internal representation.
Also known as:
- Cursor
Iterator Pattern Structure:
- Iterfaces:
- Collection
- Methods:
- createIterator() -> Creates a new iterator object specific for the underlying collection.
- Methods:
- Iterator
- Methods:
- hasNext(): Bool -> Returns true if iterator has next element.
- next() -> Get next element and advance to next element.
- current() -> Get current element. (Optional)
- remove() -> (Optional)
- Methods:
- Collection
Known uses:
- This pattern is widely in Java, C# (.NET) and C++ STL containers.
- Java
- java.util.Iterator Interface
- java.util.Enumerator interface
- .NET
- IEnumerable
- IEnumerator
Java iterator Interface:
public interface java.util.Iterator<E> { boolean hasNext(); E next(); void remove(); }
References:
- Course Notes — CIS 501: Software Architecture and Design, Fall 2014 http://softwarearch.santoslab.org/12-design-patterns-factories-iterators/index.html
- Supplement: Design Patterns. For Introduction to Java Programming - http://www.cs.armstrong.edu/liang/apcs/supplement/Supplement5hDesignpatterns.pdf
- Iterator Pattern Tutorial with Java Examples https://dzone.com/articles/design-patterns-iterator
- Iterator http://www.oodesign.com/iterator-pattern.html
- Design Patterns - Iterator Pattern https://www.tutorialspoint.com/design_pattern/iterator_pattern.htm
- Principles of Software Construction: Objects, Design, and Concurrency; Design Case Study: Stream I/O https://www.cs.cmu.edu/~charlie/courses/15-214/2014-fall/slides/14-io.pdf
- The Use of Iterator Iterator in Python https://www.codeday.top/2017/03/27/21392.html
- Examples
Example 1 - Java built-in iterator.
scala> import java.util.Arrays import java.util.Arrays scala> val arr = Arrays.asList(1, 2, 3, 4, 5, 6, 7) arr: java.util.List[Int] = [1, 2, 3, 4, 5, 6, 7] scala> val iter = arr.iterator() iter: java.util.Iterator[Int] = java.util.AbstractList$Itr@3030836d scala> iter.hasNext _ res2: () => Boolean = <function0> scala> iter.next _ res3: () => Array[Int] = <function0> scala> iter.remove _ res4: () => Unit = <function0> scala> iter.hasNext() res8: Boolean = true scala> iter.next() res9: Int = 1 scala> iter.hasNext() res10: Boolean = true scala> iter.next() res11: Int = 2 scala> iter.next() res12: Int = 3 scala> iter.next() res13: Int = 4 scala> iter.next() res14: Int = 5 scala> iter.hasNext() res15: Boolean = true scala> iter.next() res16: Int = 6 scala> iter.hasNext() res17: Boolean = true scala> iter.next() res18: Int = 7 scala> iter.hasNext() res19: Boolean = false scala> iter.next() java.util.NoSuchElementException at java.util.AbstractList$Itr.next(AbstractList.java:364) ... 32 elided scala>
Example 2 - Print all collection elements
scala> import java.util.Arrays import java.util.Arrays scala> val arr = Arrays.asList(1, 2, 3, 4, 5, 6, 7) arr: java.util.List[Int] = [1, 2, 3, 4, 5, 6, 7] scala> val iter = arr.iterator() iter: java.util.Iterator[Int] = java.util.AbstractList$Itr@210d3a42 while (iter.hasNext()) { println(iter.next()) } scala> while (iter.hasNext()) { | println(iter.next()) | } 1 2 3 4 5 6 7
Example 3: Implementation a range iterator with java.util.iterator interface.
class RangeIterator(from: Double, to: Double, step: Double) extends java.util.Iterator[Double] { private var cursor = from def hasNext() = cursor < to def next() = { if (!hasNext()) error("Error: End of iteration, no more elements available.") else { val ret = cursor cursor = cursor + step ret } } def remove() = error("Error: Method not implemented") } scala> val rng = new RangeIterator(0.0, 5.0, 1.0) rng: RangeIterator = RangeIterator@1a6df932 scala> rng.next() res41: Double = 0.0 scala> rng.hasNext() res42: Boolean = true scala> rng.next() res43: Double = 1.0 scala> rng.hasNext() res44: Boolean = true scala> rng.next() res45: Double = 2.0 scala> rng.hasNext() res46: Boolean = true scala> rng.next() res47: Double = 3.0 scala> rng.next() res48: Double = 4.0 scala> rng.hasNext() res49: Boolean = false scala> rng.next() java.lang.RuntimeException: Error: End of iteration, no more elements available. at scala.sys.package$.error(package.scala:27) at scala.Predef$.error(Predef.scala:144) at RangeIterator.next(<console>:19) ... 32 elided scala> val iter = new RangeIterator(0.0, 10.0, 1.0) while (iter.hasNext()) { println(iter.next()) } scala> while (iter.hasNext()) { | println(iter.next()) | } 0.0 1.0 2.0 3.0 4.0 5.0 6.0 7.0 8.0 9.0
Example 4: Creating an iterator to iterate over file lines.
class ReadFileLinesIterator(file: String) extends Iterator[String] { private var buf = new java.io.BufferedReader(new java.io.FileReader(file)) private var line: String = "" private var status = true init() def init() { line = buf.readLine() status = line != null } def hasNext() = status def next() = { if (hasNext()) { val ret = line line = buf.readLine() status = line != null ret } else error("Error: End of iteration. No more elements available.") } def remove() = error("Error: invalid method for this iterator.") } scala> val lineIter = new ReadFileLinesIterator("/etc/lsb-release") lineIter: ReadFileLinesIterator = non-empty iterator scala> lineIter.hasNext() res78: Boolean = true scala> lineIter.next() res79: String = DISTRIB_ID=ManjaroLinux scala> lineIter.hasNext() res80: Boolean = true scala> lineIter.next() res81: String = DISTRIB_RELEASE=17.0.1 scala> lineIter.hasNext() res82: Boolean = true scala> lineIter.next() res83: String = DISTRIB_CODENAME=Gellivara scala> lineIter.hasNext() res84: Boolean = true scala> lineIter.next() res85: String = DISTRIB_DESCRIPTION="Manjaro Linux" scala> lineIter.next() java.lang.RuntimeException: Error: End of iteration. No more elements available. at scala.sys.package$.error(package.scala:27) at scala.Predef$.error(Predef.scala:144) at ReadFileLinesIterator.next(<console>:35) ... 32 elided scala> lineIter.hasNext() res87: Boolean = false scala> val lineIter = new ReadFileLinesIterator("/etc/lsb-release") while(lineIter.hasNext()){ println(lineIter.next()) } scala> while(lineIter.hasNext()){ | println(lineIter.next()) | } DISTRIB_ID=ManjaroLinux DISTRIB_RELEASE=17.0.1 DISTRIB_CODENAME=Gellivara DISTRIB_DESCRIPTION="Manjaro Linux"
1.17.3.4 Command Design Pattern
- Overview
Intent from GOF: "Encapsulate a request as an object, thereby letting you parametrize clients with different requests, queue or log requests, and support undoable operations."
- Also known as: Action or transaction pattern.
- Encapsulate method invocations as objects called commands, which
can be stored and executed later by one or multiple invoker object
which don't know anything about the method invocation or about the
operation being performed.
- Features:
- Encapsulates method-calls as commands.
- Provide a common interface for method calls.
Participants:
- Receiver - Object whose methods will be invoked indirectly by the objects implementing the ICommand interface. This name "receiver" comes from Smalltalk and means the object which receives the message (method call).
- Invoker(s) - (Could also be called Sender). One or more object
that store the command objects and executes them later. The
invoker must have no knowledge about how to perform the method
call.
- The invoker objects can be a list of commands to be run; a stack of commands; user interface elements such as buttons, menus and etc, where each element stores a command that performs a application invocation such as App.print(), App.hidePanel(panel1).
- ICommand - Command interface - Interface encapsulating the request or method invocation.
- Client - Client means any code using a particular code, api, interface or object, in this case any code using this code. The client's role is to create the concrete commands setting their requests (methods of receiver that they invoke) and store the commands in the invoker(s).
Use cases:
- Grapical User Interfaces: Input UI elements such as buttons, menus and key bindings could act as invokers storing a high level application command such as "save document", "print document", "exit application" and so on. This approach allows to a separate the user interface from the application by decoupling the UI element that executes the command from the logic performed by the command and also allows a single command to be executed by multiple UI elements. For instance, an action such as "save document" could be encapsulated as a command object bound to many UI sources such the key binding ctr-s, a save button and a save menu item, as a result, any changes in the user interface code would not break the main application code.
- Allow redo/undo
- Transactions
- Replace switch statements
Possible Variations
- Single invoker
- Multiple invokers
- Single receiver
- Multiple receivers
References and further reading
- Command Pattern Tutorial with Java Examples - https://dzone.com/articles/design-patterns-command
- Toni Sellarès - The Command Pattern http://ima.udg.edu/~sellares/EINF-ES1/CommandToni.pdf
- Hans Vangheluwe and Alexandre Denault Design Patters - Command Pattern http://msdl.cs.mcgill.ca/people/hv/teaching/SoftwareDesign/lectures/lecture.command/lecture.command.pdf
- Command Pattern with Undo/Redo
- Design Patterns: Command Pattern - CodeProject
- Examples of Command Design pattern in .NET WPF Frameworks:
- Example 1 - Canvas drawing application with animation
In this example, a graphical 2D animation application has tree main parts, a canvas where 2D shapes can be drawn, a scheduler that can that allows to schedule drawing commands to be executed in a given time interval and a command interface which encapsulates request to the canvas.
It is clear that, the canvas plays the role of the receiver and scheduler plays the role of the invoker. The client code role in this case is create the concrete commands and pass the to the scheduler in order to create 2D animations.
OOP Approach
/** Receiver object */ class Canvas { def drawLine(x1: Int, y1: Int, x2: Int, y2: Int) = println(s"Draw line from p1 = (x1: $x1, y1: $y1) to p2 = (x2: $x2, y2: $y2)") def drawCircle(x: Int, y: Int, radius: Int) = println(s"Draw ellipse at (x = $x, y = $y) with radius = $radius") def drawSquare(x: Int, y: Int, size: Int) = { println(s"Draw square at center ($x, $y) with size = $size") } def clear() = println("Clear canvas") } /** Command Interface */ trait ICommand{ def execute(): Unit } class Scheduler{ import scala.collection.mutable.ListBuffer private val _cmdlist = ListBuffer[(Int, ICommand)]() // Delay in milliseconds def addCommand(delayMs: Int, cmd: ICommand) = { _cmdlist.append((delayMs, cmd)) } def clearCommands() = _cmdlist.clear() def playCommands() = for ((time, cmd) <- _cmdlist){ Thread.sleep(time) cmd.execute() } } /* ============== Concrete commands ============== */ class DrawLine( canvas: Canvas, x1: Int, y1: Int, x2: Int, y2: Int ) extends ICommand { def execute() = canvas.drawLine(x1, y1, x2, y2) } // Possible to write arguments in many styles, // including this quasi-tabular style. class DrawCircle( canvas: Canvas, x: Int, y: Int, radius: Int ) extends ICommand { def execute() = canvas.drawCircle(x, y, radius) } class DrawSquare(canvas: Canvas, x: Int, y: Int, size: Int) extends ICommand{ def execute() = canvas.drawSquare(x, y, size) } /** =============== Client Code =============== */ // Creates animation by setting the commands and scheduler // val canvas = new Canvas() val invoker = new Scheduler() val drawCmd1 = new DrawLine(canvas, 2, 10, 5, 6); val drawCmd2 = new DrawCircle(canvas, 5, 6, 20); val drawCmd3 = new DrawSquare(canvas, 9, 8, 34); invoker.addCommand(1500, drawCmd1) // Execute command 1 after 1.5 seconds invoker.addCommand(1000, drawCmd2) // Execute command 2 after 2 seconds invoker.addCommand(2000, drawCmd3) // Execute command 3 after 3 seconds scala> invoker.playCommands() Draw line from p1 = (x1: 2, y1: 10) to p2 = (x2: 5, y2: 6) Draw ellipse at (x = 5, y = 6) with radius = 20 Draw square at center (9, 8) with size = 34
FP Approach
- The disadvantage of the OO code is that it is necessary to create a new class for every method of the receiver object what will result in many files and lots of boilerplate code.
Approach 1: Replace the interface ICommand with a function:
/** Receiver object */ class Canvas { def drawLine(x1: Int, y1: Int, x2: Int, y2: Int) = println(s"Draw line from p1 = (x1: $x1, y1: $y1) to p2 = (x2: $x2, y2: $y2)") def drawCircle(x: Int, y: Int, radius: Int) = println(s"Draw ellipse at (x = $x, y = $y) with radius = $radius") def drawSquare(x: Int, y: Int, size: Int) = { println(s"Draw square at center ($x, $y) with size = $size") } def clear() = println("Clear canvas") } object ICommandTypes{ type ICommand = () => Unit } import ICommandTypes._ class Scheduler{ import scala.collection.mutable.ListBuffer private val _cmdlist = ListBuffer[(Int, ICommand)]() // Delay in milliseconds def addCommand(delayMs: Int, cmd: ICommand) = { _cmdlist.append((delayMs, cmd)) } def clearCommands() = _cmdlist.clear() def playCommands() = for ((time, cmd) <- _cmdlist){ Thread.sleep(time) cmd() } } /** =============== Client Code =============== */ // // Creates animation by setting the commands and the scheduler // val canvas = new Canvas() val invoker = new Scheduler() invoker.addCommand(1500, () => canvas.drawLine(2, 10, 5, 6)) invoker.addCommand(1000, () => canvas.drawCircle(5, 6, 20)) invoker.addCommand(2000, () => canvas.drawSquare(9 ,8 , 34)) scala> invoker.playCommands() Draw line from p1 = (x1: 2, y1: 10) to p2 = (x2: 5, y2: 6) Draw ellipse at (x = 5, y = 6) with radius = 20 Draw square at center (9, 8) with size = 34 // ---> Or it could also be <---- // val canvas = new Canvas() val invoker = new Scheduler() val cmd1 = () => canvas.drawLine(2, 10, 5, 6) val cmd2 = () => canvas.drawCircle(5, 6, 20) val cmd3 = () => canvas.drawSquare(9 ,8 , 34) invoker.addCommand(1500, cmd1) invoker.addCommand(1000, cmd2) invoker.addCommand(2000, cmd3) scala> invoker.playCommands() Draw line from p1 = (x1: 2, y1: 10) to p2 = (x2: 5, y2: 6) Draw ellipse at (x = 5, y = 6) with radius = 20 Draw square at center (9, 8) with size = 34 // ---> Or it could also be <---- // def makeCmdDrawCircle(canvas: Canvas, x: Int, y: Int, radius: Int) = () => canvas.drawCircle(x, y, radius) scala> val drawCircleAtOrigin = makeCmdDrawCircle(canvas, 0, 0, 25) drawCircleAtOrigin: () => Unit scala> invoker.addCommand(4000, drawCircleAtOrigin)
- Example 2 - Graphical user interface with FP approach
In this example, every GUI button works as an invoker and main application as a receiver. The command pattern here can decouple the application from the GUI.
- Running: The sample code can be run directly from the REPL with :paste and pasting the source code or loading the file with :paste src/commandGUIPattern.scala.
scala> :paste src/commandGUIPattern.scala // User clicks at buttons or menu items. scala> Save document to file file1.txt Save document to file file1.txt Save document to file file1.txt Open file = /data/fileTest.csv Open file = /data/fileTest.csv Fake command. DO NOT EXIT during test. Fake command. DO NOT EXIT during test. Fake command. DO NOT EXIT during test. // Run command directly scala> cmdSave.execute() Save document to file file1.txt scala> cmdOpen.execute() Open file = /data/fileTest.csv
- Screenshot:
import javax.swing.{JFrame, JButton, JMenuItem, JMenuBar, JMenu} import java.awt.event.{ActionListener, ActionEvent} class Application{ def saveDocument(file: String, data: String) = println(s"Save document to file $file") def openFile(file: String) = println(s"Open file = $file") def printDocument() = println("Printing document ...") def exit() = { println("Shutdown systems ...") System.exit(0) } } trait ICommand extends ActionListener{ // Abstract method def execute(): Unit // Concrete method def actionPerformed(evt: ActionEvent) = execute() } class MainGUI extends JFrame{ private val btSave = new JButton("Save") private val btOpen = new JButton("Open") private val btClose = new JButton("Close") private val menuSave = new JMenuItem("Save") private val menuOpen = new JMenuItem("Open") private val menuClose = new JMenuItem("Close") init() def init(){ setLayout(new java.awt.FlowLayout()) setTitle("FP - Command Design Pattern for GUIs") // Can be without this, but explicit is better than implicit! // this.settSize(300, 276) setSize(300, 276) // Add buttons //============== this.add(btSave) this.add(btOpen) this.add(btClose) // Add menu bar //================ val menu = new JMenu("Save") menu.add(menuOpen) menu.add(menuSave) menu.add(menuClose) val menuBar = new JMenuBar() menuBar.add(menu) setJMenuBar(menuBar) } def setSaveCommand(cmd: ICommand) = { btSave.addActionListener(cmd) menuSave.addActionListener(cmd) this // return this for method chaining } def setOpenCommand(cmd: ICommand) = { btOpen.addActionListener(cmd) menuOpen.addActionListener(cmd) this } def setExitCommand(cmd: ICommand) = { btClose.addActionListener(cmd) menuClose.addActionListener(cmd) this } } //--- End of class MainGUI ---- // // Approach 1: The function generates anonymous // classes implementing the interface. // def makeCommand(action: => Unit) = new ICommand{ def execute() = action } def makeOpenComand(app: Application, file: String) = new ICommand{ def execute() = app.openFile(file) } val app = new Application() val gui = new MainGUI() gui.setVisible(true) val cmdSave = makeCommand{ app.saveDocument("file1.txt", "some data") } val cmdOpen = makeOpenComand(app, "/data/fileTest.csv") val cmdExit = makeCommand{ println("Fake command. DO NOT EXIT during test.")} gui.setSaveCommand(cmdSave) gui.setOpenCommand(cmdOpen) gui.setExitCommand(cmdExit)
1.17.3.5 Observer / Publisher Subscriber
- Overview
The observer pattern is stated by GOF as "Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically."
The observer pattern is behavioral pattern defining a one-to-many dependency between subject and observer. The observers objects are notified about the subject changes.
This pattern is widely used by GUIs toolkits, including Java Swing and spreadsheets. It is also a key part of MVC - Model View Controller.
It is also known as:
- Puhlisher/ Subscriber
- Observable/ Observer
- Subject / Observer
Known uses in Frameworks:
- Java: java.util.Observable
- .NET: System.IObservable and System.IObserver
Participants:
- Subject - The subject object maintains references to all
subscribed observers and notifies them when an event happens or
its state changes.
- Methods:
- attach or subscribe - Attach observer.
- detach/ unsubscribe or remove - Remove observer.
- notify - Notify all observers,
- Methods:
- Observer - Each Observer object has an update or notify method
that is called by the subscribed subject when its states
changes.
- Methods:
- update / notify - Notify or update observer.
- Methods:
Mechanisms to send data to observers:
- Pull model. Observer executes Subject.getData() to get data from subject.
- Push model. The subject pass its data as argument of observers'
update method.
for o in observers do o.update(subject.data)
References:
- Observer Pattern http://www.cs.mcgill.ca/~hv/classes/CS400/01.hchen/doc/observer/observer.html
- Observer Design Pattern https://docs.microsoft.com/en-us/dotnet/standard/events/observer-design-pattern
- Exploring the Observer Design Pattern https://msdn.microsoft.com/en-us/library/ee817669.aspx
- Introduction to Software Engineering - The Observer Design Pattern http://stg-tud.github.io/eise/WS11-EiSE-17-Observer_Design_Pattern.pdf
- Observer http://cs.unb.ca/~wdu/cs4015/ch5i.pdf
- Observer design pattern https://www.slideshare.net/saratorkey/observer-design-pattern
- Software Development (cs2500) http://www.ucccs.info/ucc/ucc2/ucc_folder/cs2500/notes/Lecture%2037%20-%20EventHandlers.pdf
- Observer pattern https://en.wikipedia.org/wiki/Observer_pattern
- Example in OO way
Example: A temperature sensor publishes its temperature to 4 observers that displays the temperature in Celsius, Kelvin and Fahrenheit. It has also a GUI observer (View) that displays the temperature in Celsius. All observers(displays, aka views) are updated when the temperature changes.
// Observer interface trait Observer[A] { def update(a: A) } // Subject interface trait Subject[A] { // Subscribe observer to subject updates def attach(obs: Observer[A]): Unit // Remove/ unsubscribe observer from subject updates def detach(obs: Observer[A]): Unit // Notify all observers def notifyObservers(a: A): Unit } // Concrete: Subject //---------------------------------- // // Publishes temperature measurements. class TempSensor extends Subject[Double]{ private var temp: Double = 0.0 private var observers: Set[Observer[Double]] = Set() def attach(obs: Observer[Double]) = observers += obs def detach(obs: Observer[Double]) = observers -= obs def notifyObservers(tempNew: Double) = { temp = tempNew observers.foreach(_.update(tempNew)) // for (obs <- observers) obs.update(tempNew) } def getTemp() = temp } // ---------- Concrete Observers ----------------- // class ConsoleCelsiusObserver extends Observer[Double]{ def update(temp: Double) = { printf("Current temperature is %.3f in °C\n", temp) } } // End of class ConsoleCelsiusObserver class ConsoleKelvinObserver extends Observer[Double]{ def update(temp: Double) = { printf("Current temperature is %.3f in °K\n", temp + 273.0) } } // End of class ConsoleKelvinObserver class ConsoleFahrenheitObserver(subject: Subject[Double]) extends Observer[Double]{ init() def init() { subject.attach(this) } def update(temp: Double) = { val tempF = 5.0 / 9.0 * temp + 32.0 printf("Current temperature is %.3f in F\n", tempF) } } // End of class ConsoleFahrenheitObserver /// Java Swing Observer class GuiCelsiusObserver(subject: Subject[Double]) extends Observer[Double] { private var frame = new javax.swing.JFrame() private var display = new javax.swing.JLabel() init() def init(){ frame.setSize(255, 71) frame.add(display) frame.show() frame.setTitle("Temperature View") subject.attach(this) } def update(temp: Double) = { display.setText("Temperature = %.3f C".format(temp)) } } /// ----------- Classes Instantiation ---------- /// val sensor = new TempSensor() val consoleC = new ConsoleCelsiusObserver() sensor.attach(consoleC) val consoleK = new ConsoleKelvinObserver() sensor.attach(consoleK) val consoleF = new ConsoleFahrenheitObserver(sensor) val guiObs = new GuiCelsiusObserver(sensor)
Running:
scala> :paste src/observerPattern1.scala Pasting file src/observerPattern1.scala... warning: there was one deprecation warning; re-run with -deprecation for details ob_scala_eoldefined trait Observer defined trait Subject defined class TempSensor defined class ConsoleCelsiusObserver defined class ConsoleKelvinObserver defined class ConsoleFahrenheitObserver defined class GuiCelsiusObserver sensor: TempSensor = TempSensor@4dd02341 consoleC: ConsoleCelsiusObserver = ConsoleCelsiusObserver@3212a8d7 consoleK: ConsoleKelvinObserver = ConsoleKelvinObserver@7a1a3478 consoleF: ConsoleFahrenheitObserver = ConsoleFahrenheitObserver@495b0487 guiObs: GuiCelsiusObserver = GuiCelsiusObserver@55dfcc6 // Manual simulation //------------------------------- scala> sensor.notifyObservers(20.0) Current temperature is 20.000 in °C Current temperature is 293.000 in °K Current temperature is 43.111 in F scala> sensor.notifyObservers(20.1) Current temperature is 20.100 in °C Current temperature is 293.100 in °K Current temperature is 43.167 in F scala> sensor.notifyObservers(20.5) Current temperature is 20.500 in °C Current temperature is 293.500 in °K Current temperature is 43.389 in F scala> sensor.detach(consoleK) scala> sensor.notifyObservers(23.0) Current temperature is 23.000 in °C Current temperature is 44.778 in F // Automatic simulation //-------------------------------------- def runTimer(interval: Int, taskFn: () => Unit) = { val task = new java.util.TimerTask() { def run() { taskFn() } } val timer = new java.util.Timer() // Run the task every 1 second interval (or 1000 milli seconds) timer.schedule(task, 1, interval) timer } // End of runTimer // Generates a random number within an interval // from (mid - delta) to (mid + delta) // def genRandomInterval(mid: Double, delta: Double) = { val rnd = new java.util.Random() () => { val x = rnd.nextDouble() 2 * delta * x + (mid - delta) } } scala> def genRandomInterval(mid: Double, delta: Double) = { | val rnd = new java.util.Random() | () => { val x = rnd.nextDouble() | 2 * delta * x + (mid - delta) | } | } genRandomInterval: (mid: Double, delta: Double)() => Double // Generates a temperature between 20.0 - 3.0 (17.0 C) and 20.0 + 3.0 (23 °C) scala> val genTemp = genRandomInterval(20.0, 3.0) genTemp: () => Double = <function0> // Update the temperature every 1 second runTimer(1000, () => sensor.notifyObservers(genTemp()))
- Example in FP way
As Scala is also a functional language, it allows functions be passed directly as argument, therefore the observer objects can be replaced by callbacks functions and the subject by some object with a mutable collection of callbacks.
The subject or observable class was replaced by a record of stateful functions created with a closure, but it could remain as class as well.
File: src/observerPatternFun.scala
// Observer object is replace by a callback function type Observer[A] = A => Unit // Record containing functions case class Subject[A]( attach: Observer[A] => Unit ,detach: Observer[A] => Unit ,notifyObservers: A => Unit ,getState: () => A ) def createSubject[A](stateInit: A) = { var observers : Set[A => Unit] = Set() var state = stateInit val attach = (obs: Observer[A]) => { observers += obs } val detach = (obs: Observer[A]) => { observers -= obs } val notifyObservers = (a: A) => { state = a for (obs <- observers) obs(a) } val getState = () => state Subject(attach, detach, notifyObservers, getState) } // The observer becomes just a function or callback!! def consoleCelsiusObserver(temp: Double) = { printf("Current temperature is %.3f in °C\n", temp) } def consoleKelvinObserver(temp: Double) = { printf("Current temperature is %.3f in °K\n", temp + 273.0) } def consoleFahrenheitObserver(temp: Double) = { val tempF = 5.0 / 9.0 * temp + 32.0 printf("Current temperature is %.3f in F\n", tempF) } // Creates a function that updates the GUI display // // Note: (Double => Unit) is optional. // The type annotation was added to make reading easier. // def makeGuiObserver(): (Double => Unit) = { val frame = new javax.swing.JFrame() val display = new javax.swing.JLabel() frame.setSize(255, 71) frame.add(display) frame.show() frame.setTitle("Temperature View") (temp: Double) => display.setText("Temperature = %.3f C".format(temp)) } val sensor = createSubject[Double](20.0) sensor.attach(consoleCelsiusObserver) sensor.attach(consoleKelvinObserver) sensor.attach(consoleFahrenheitObserver) val guiObserver = makeGuiObserver() sensor.attach(guiObserver)
Running:
scala> :paste src/observerPatternFun.scala Pasting file src/observerPatternFun.scala... warning: there was one deprecation warning; re-run with -deprecation for details ob_scala_eoldefined type alias Observer defined class Subject createSubject: [A](stateInit: A)Subject[A] consoleCelsiusObserver: (temp: Double)Unit consoleKelvinObserver: (temp: Double)Unit consoleFahrenheitObserver: (temp: Double)Unit makeGuiObserver: ()Double => Unit sensor: Subject[Double] = Subject(<function1>,<function1>,<function1>,<function0>) guiObserver: Double => Unit = <function1> // Manual simulation //-------------------------------------- scala> sensor.notifyObservers(21.0) Current temperature is 21.000 in °C Current temperature is 294.000 in °K Current temperature is 43.667 in F scala> sensor.notifyObservers(22.0) Current temperature is 22.000 in °C Current temperature is 295.000 in °K Current temperature is 44.222 in F scala> sensor.notifyObservers(23.0) Current temperature is 23.000 in °C Current temperature is 296.000 in °K Current temperature is 44.778 in F scala> sensor.notifyObservers(25.0) Current temperature is 25.000 in °C Current temperature is 298.000 in °K Current temperature is 45.889 in F // Automatic simulation //-------------------------------------- def runTimer(interval: Int, taskFn: () => Unit) = { val task = new java.util.TimerTask() { def run() { taskFn() } } val timer = new java.util.Timer() // Run the task every 1 second interval (or 1000 milli seconds) timer.schedule(task, 1, interval) timer } // End of runTimer // Generates a random number within an interval // from (mid - delta) to (mid + delta) // def genRandomInterval(mid: Double, delta: Double) = { val rnd = new java.util.Random() () => { val x = rnd.nextDouble() 2 * delta * x + (mid - delta) } } scala> val genTemp = genRandomInterval(20.0, 3.0) genTemp: () => Double = <function0> scala> genTemp() res5: Double = 19.81265553013229 scala> genTemp() res6: Double = 18.965575803660617 scala> genTemp() res7: Double = 21.172101549312305 scala> genTemp() res8: Double = 17.821626339663855 scala> genTemp() res9: Double = 22.38493758505381 // Update the temperature every 1 second runTimer(1000, () => sensor.notifyObservers(genTemp()))
- Java built-in Observer Pattern
Java provides the java.util.Observable and java.util.Observer interfaces to help implement the observer pattern.
The subject/observable class inherits the Observable interface and notifies observer objects which implements the Observer interface.
Example: Every time the temperature sensor gets a new temperature, the celsius and kelvin displays are updated with the temperature measurements.
class TemperatureSensor(initTemp: Double) extends java.util.Observable { private var temp = initTemp def setValue(t: Double) { temp = t setChanged() notifyObservers() } def getValue() = temp } // Observer that displays temperature in Celsius class CelsiusObserver extends java.util.Observer { def update(obs: java.util.Observable, obj: Object){ val ov = obs.asInstanceOf[TemperatureSensor] println("Temperature is = %.2f C".format(ov.getValue())) } } // Observer that displays temperature in Kelvin class KelvinObserver extends java.util.Observer { def update(obs: java.util.Observable, obj: Object){ val ov = obs.asInstanceOf[TemperatureSensor] val temp = ov.getValue() + 273.0 println("Temperature is = %.2f K".format(temp)) } } val sensor = new TemperatureSensor(21.0) val celsiusObserver = new CelsiusObserver() val kelvinObserver = new KelvinObserver() sensor.addObserver(celsiusObserver) sensor.addObserver(kelvinObserver) cala> sensor.getValue() res19: Double = 21.0 scala> sensor.setValue(21.5) Temperature is = 294.50 K Temperature is = 21.50 C scala> sensor.setValue(21.6) Temperature is = 294.60 K Temperature is = 21.60 C scala> sensor.setValue(22) Temperature is = 295.00 K Temperature is = 22.00 C scala> scala> sensor.countObservers() res23: Int = 2 scala> sensor.deleteObserver(kelvinObserver) scala> sensor.setValue(23) Temperature is = 23.00 C scala> sensor.deleteObservers() scala> sensor.setValue(100)
References:
- Observer and Observable An introduction to the Observer interface and Observable class using the Model/View/Controller architecture as a guide. http://www.javaworld.com/article/2077258/learn-java/observer-and-observable.html
- java.util.Observer Example https://examples.javacodegeeks.com/core-java/util/observer/java-util-observer-example/
1.17.3.6 Model View Controller - MVC
- Overview
The MVC - Model-View-Controller pattern was introduced by Trygve Reenskaug in a Smalltalk-80 implementation in 1970s to address the problem of building GUI - Graphical User Interfaces.
The MVC has three parts:
- View - The view is everything the user can see and it is
resposible to render the model data. It is built with a hierarchy of
widgets of some GUI toolkit such as Gtk, QT, Java Swing and etc.
- Manages graphical or textual output.
- Controller - The controller is a middleware between the view and
model. The controller handles the user input or the events
triggered by the view updating the model or the view.
- Manages user input or GUI events such as button click, mouse move and etc.
- Model - The model contains data to be displayed by the view and
the business logic.
- Manages application logic or domain logic.
- The model must have no knowledge about the view or controller.
- A model can have multiple views diplaying the model in different ways. Example bar chart, pie chart, table, spreadsheet display and so on. Each view must have an associated controller.
- When the model is updated, it notifies all views that it has changed and the views query data from the model updating itself. The model is suitable to be implemented with observer pattern.
- The MVC is supposed to have only one global model.
MVC Variation:
- Controller as mediator or MVP - Model View Presenter. The model doesn't interact directly with the view. It updates the view through the controller. This MVC variation is widely used in Apple's Cocoa framework.
Benefits:
- Separate the domain model from the presentation.
- The same data can be displayed or rendered in many different ways. Multiple views for short.
Drawbacks:
- In a GUI it is hard to separate input from output, consequently it is hard to separate the view from the model. What makes them tightly coupled.
References and Bookmarks:
- MVP: Model-View-Presenter. The Taligent Programming Model for C++ and Java https://web.archive.org/web/20170620100654/http://www.wildcrest.com/Potel/Portfolio/mvp.pdf
- A Description of the Model-View-Controller User Interface Paradigm in the Smalltalk-80 System https://web.archive.org/web/20170620095511/http://www.global-webnet.com/Adventures/Files/DescriptionOfMvcUi-KrasnerPope.pdf
- Java SE Application Design With MVC https://web.archive.org/web/20070309124616/http://java.sun.com/developer/technicalArticles/javase/mvc/
- ARTICLE TITLE: You’ve got the model-view-controller https://web.archive.org/web/20170620142643/http://cs.txstate.edu/~rp31/papers/Model-View-Controller.PDF
- Model View Controller http://wiki.c2.com?ModelViewController
- Model-View-Controller paradigm; Observer pattern; Creating Graphical User Interfaces in Java/Swing - https://drive.google.com/viewerng/viewer?url=http://faculty.washington.edu/stepp/courses/2005spring/tcss360/lectures/notes/08-gui_mvc_observer.ppt
- Model-View-Controller Pattern - http://academic.regis.edu/dbahr/generalpages/softwareengineering/softwareengpart17.pdf
Apple's "MVC":
- Guides and Samples - Model-View-Controller https://developer.apple.com/library/content/documentation/General/Conceptual/DevPedia-CocoaCore/MVC.html
- Looking at Model-View-Controller in Cocoa https://www.cocoawithlove.com/blog/mvc-and-cocoa.html
Sample codes and examples:
- Model-View-Controller (MVC) Structure https://www.leepoint.net/GUI/structure/40mvc.html
- Lecture 6: Coupling and the MVC software architecture https://web.archive.org/web/20170620171835/http://people.cs.ksu.edu/~schmidt/501s14/Lectures/Lecture06S.html
- Building Games Using the MVC Pattern – Tutorial and Introduction https://www.javacodegeeks.com/2012/02/building-games-using-mvc-pattern.html
- Sliding Clock Using Java Swing http://java-articles.info/articles/?p=885
Java Swing:
- Java Programming Tutorial. Programming Graphical User Interface (GUI) - Part 2 https://www3.ntu.edu.sg/home/ehchua/programming/java/J4a_GUI_2.html
History:
- Next Step http://wiki.c2.com?NextStep
- Open Step http://wiki.c2.com?OpenStep
- View - The view is everything the user can see and it is
resposible to render the model data. It is built with a hierarchy of
widgets of some GUI toolkit such as Gtk, QT, Java Swing and etc.
- Example
This example shows a simple GUI application with MVC and Java Swing toolkit using three views, one console view that prints the model state and two Java swing GUI views.
File: src/mvcCounter.scala
import javax.swing.{JFrame, JPanel, JTextField, JButton, JLabel} //----------- Observer Pattern Interfaces ----------- // // Observer interface trait Observer { def update(): Unit } // Subject or Observable interface trait Observable { private var observers: Set[Observer] = Set() // Subscribe observer to subject updates def attach(obs: Observer) { observers += obs } // Remove/ unsubscribe observer from subject updates def detach(obs: Observer) { observers -= obs } // Notify all observers def notifyObservers() { for (obs <- observers) obs.update() } } //----------- Helpers to subscribe to events ------------- // /// Register callback function /// def onClick(button: JButton) (handler: () => Unit) = { button.addActionListener( new java.awt.event.ActionListener(){ def actionPerformed(evt: java.awt.event.ActionEvent) = { handler() } } ) } def onWindowExit(frame: javax.swing.JFrame) (handler: () => Unit) = { frame.addWindowListener( new java.awt.event.WindowAdapter(){ override def windowClosing(evt: java.awt.event.WindowEvent) = { handler() } }) } def showFrameSize(frame: javax.swing.JFrame){ println(frame.getSize()) } //--------------- MVC Counter Demo -------------------- // class CounterModel(init: Int) extends Observable{ private var counter = init def getValue() = counter def increment() = { counter = counter + 1 // Every time the model state is changed, // the observers must be notified. this.notifyObservers() // the 'this' prefix is optional. } def decrement() = { counter = counter - 1 this.notifyObservers() } } /// This view doesn't need a controller as this doesn't need an input. class ConsoleView(counter: CounterModel) extends Observer{ init() def init(){ // Register observer (this class) counter.attach(this) } // updates the view def update() { println("Counter value is = " + counter.getValue()) } } class GuiView1(counter: CounterModel) extends Observer{ private val frame = new JFrame("Counter MVC App") private val panel = new JPanel(new java.awt.FlowLayout()) private val label = new JLabel("Counter") private val display = new JTextField(10) private val btnInc = new JButton("Increment") private val btnDec = new JButton("Decrement") private val btnExit = new JButton("Exit") init() def init(){ // Register observer (this class) counter.attach(this) panel.add(label) panel.add(display) panel.add(btnInc) panel.add(btnDec) panel.add(btnExit) display.setEditable(false) frame.add(panel) frame.setSize(600, 100) frame.show() // Exit application if user closes window. frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE) this.update() } // updates the view def update() { display.setText(counter.getValue().toString()) } def onIncrement(handler: () => Unit) { onClick(btnInc)(handler) } def onDecrement(handler: () => Unit) { onClick(btnDec)(handler) } def onExit(handler: () => Unit) = { onClick(btnExit)(handler) } def show(){ frame.show() } } class GuiView1Controller(viewp: GuiView1, modelp: CounterModel){ private var view = viewp private var model = modelp init() def init(){ //---> Model Manipulation view.onIncrement(this.increment) view.onDecrement(this.decrement) //---> GUI manipulation view.onExit(() => System.exit(0)) } def increment(){ model.increment() } def decrement(){ model.decrement() } } // View without input that only displays the model. // This view doesn't need a controller as it doesn't // have any user input. // class GuiView2(counter: CounterModel) extends Observer{ private val frame = new JFrame("Counter App - Display only view") private val panel = new JPanel() private val display = new JLabel() init() def init(){ counter.attach(this) panel.add(display) frame.setSize(354, 54) frame.add(panel) frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE) frame.show() update() } // Method required by Observer interface def update(){ display.setText(counter.getValue().toString()) // showFrameSize(frame) } } val counterModel = new CounterModel(0) val consoleView = new ConsoleView(counterModel) val guiView1 = new GuiView1(counterModel) val guiView1Controller = new GuiView1Controller(guiView1, counterModel) val guiView2 = new GuiView2(counterModel)
Running:
$ scala mvcCounter.scala warning: there were three deprecation warnings; re-run with -deprecation for details one warning found Counter value is = 1 Counter value is = 2 Counter value is = 3 Counter value is = 4 ... ...
1.17.3.7 Model View Presenter with Passive View - MVP
- Overview
The MVP - Model-View-Presenter is variation of MVC Model-View-Controller which the model doesn't communicate directly with the view and it is made passive. This pattern is often designated as MVC.
View event triggered by user. +.....->>>-.......+ +........<<<<..........+ / (User action) \ / Model notification \ / \ / event \ View Presenter Model \ / \ / \ View update / \ Model update / +----<<<-----------/ +--->>>------>>>--------+ Legend: ...... - Event or notification ------ - Method call or action.
- Model
- The model is responsible for the business logic or domain logic and application state.
- As in the MVC, the model must have no knowledge about the view or presenter.
- Unlike the MVC, the model must not communicate directly with the view, instead it must notify the presenter that it has changed. It can be done using the observer pattern with the model playing the role of observable or observer and the presenter acting as observer.
- View
- The view role is display the model.
- Passive view:
- The view must not have any knowledge about the model and not access it directly.
- The view doesn't update itself from the model. It is updated by the presenter.
- The view delegates use input events to the presenter.
- Presenter
- The presenter role is to handle the presentation logic and mediate the communication between the model and view updating the view with the model data and also handling view events and model notifications.
- Presenter - View communication:
- The presenter handles events received from the view and manipulates the view or the model.
- Presenter - Model communication:
- The presenter receives notifications from the model and updates the view with model data in suitable format to the view.
References and Bookmarks:
- Martin Fowler. Pasive View https://martinfowler.com/eaaDev/PassiveScreen.html
- Andy Bower and Blair McGlashan. The evolution of the Dolphin Smalltalk MVP application framework. https://web.archive.org/web/20170622165759/http://www.object-arts.com/downloads/papers/TwistingTheTriad.PDF
- Tutorial 5: MVC Patterns http://griffon-framework.org/tutorials/5_mvc_patterns.html or https://web.archive.org/web/20170622172050/http://griffon-framework.org/tutorials/5_mvc_patterns.html
- Yang Zhang and Yanjing Luo An Architecture and Implement Model for Model-View-Presenter Pattern https://web.archive.org/web/20170622174219/http://www.meeting.edu.cn/meeting/UploadPapers/1282707447171.pdf
- Model-View-Presenter: Looking at Passive View http://blogs.lessthandot.com/index.php/architect/designingsoftware/model-view-presenter-looking-at-passive/
- Aviad Ezra. Twisting the MVC Triad - Model View Presenter (MVP) Design Pattern http://aviadezra.blogspot.com.br/2007/07/twisting-mvp-triad-say-hello-to-mvpc.html or https://web.archive.org/web/20170622180300/http://aviadezra.blogspot.com.br/2007/07/twisting-mvp-triad-say-hello-to-mvpc.html
- Apple Inc. Concepts in Objective-C Programming - Model-View-Controller https://web.archive.org/web/20170622181821/https://developer.apple.com/library/content/documentation/General/Conceptual/CocoaEncyclopedia/Model-View-Controller/Model-View-Controller.html
- Model-View-Controller rationale, implementation, abstract model widgets https://web.archive.org/web/20170622183803/https://www.student.cs.uwaterloo.ca/~cs349/s14/files/04.mvc.3up.pdf
- Model
- Example
This example is similar to the MVC counter example.
File: src/mvpCounter.scala
import javax.swing.{JFrame, JPanel, JTextField, JButton, JLabel} //----------- Observer Pattern Interfaces ----------- // // Observer interface trait Observer { def update(): Unit } // Subject or Observable interface trait Observable { private var observers: Set[Observer] = Set() // Subscribe observer to subject updates def attach(obs: Observer) { observers += obs } // Remove/ unsubscribe observer from subject updates def detach(obs: Observer) { observers -= obs } // Notify all observers def notifyObservers() { for (obs <- observers) obs.update() } } //----------- Helpers to subscribe to events ------------- // /// Register callback function /// def onClick(button: JButton) (handler: () => Unit) = { button.addActionListener( new java.awt.event.ActionListener(){ def actionPerformed(evt: java.awt.event.ActionEvent) = { handler() } } ) } def onWindowExit(frame: javax.swing.JFrame) (handler: () => Unit) = { frame.addWindowListener( new java.awt.event.WindowAdapter(){ override def windowClosing(evt: java.awt.event.WindowEvent) = { handler() } }) } //--------------- MVP Counter Demo -------------------- // class CounterModel(init: Int) extends Observable{ private var counter = init def getValue() = counter def increment() = { counter = counter + 1 // Every time the model state is changed, // the observers must be notified. this.notifyObservers() // the 'this' prefix is optional. } def decrement() = { counter = counter - 1 this.notifyObservers() } } class CounterView extends { private val frame = new JFrame("Counter MVC App") private val panel = new JPanel(new java.awt.FlowLayout()) private val label = new JLabel("Counter") private val display = new JTextField(10) private val btnInc = new JButton("Increment") private val btnDec = new JButton("Decrement") private val btnExit = new JButton("Exit") init() def init(){ panel.add(label) panel.add(display) panel.add(btnInc) panel.add(btnDec) panel.add(btnExit) display.setEditable(false) frame.add(panel) frame.setSize(600, 100) // Exit application if user closes window. frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE) } // updates the view def setDisplay(value: String) { display.setText(value) } def onIncrement(handler: () => Unit) { onClick(btnInc)(handler) } def onDecrement(handler: () => Unit) { onClick(btnDec)(handler) } def onExit(handler: () => Unit) = { onClick(btnExit)(handler) } def show(){ frame.show() } } class CounterPresenter(model: CounterModel) extends Observer{ private val view = new CounterView() init() def init(){ // Subscribe presenter to model updates model.attach(this) //---> Model Manipulation view.onIncrement(this.increment) view.onDecrement(this.decrement) //---> GUI manipulation view.onExit(() => System.exit(0)) // Initial model display this.update() } def increment(){ model.increment() } def decrement(){ model.decrement() } // Method required by Observer interface. It updates the view // when the model notifies the presenter that its state has // changed. def update(){ view.setDisplay(model.getValue().toString()) } def show() = view.show() } val counterModel = new CounterModel(0) val presenter = new CounterPresenter(counterModel) presenter.show()
2 Bookmarks and Resources
2.1 Scala API Documentation
2.2 Learning Resources
2.3 Java and Scala Libraries
2.3.1 Scala Libraries
Selection of third party Scala libraries.
- Database
- Category Theory and Functional Programming
- Reactive Programming - (Event-driven programming)
- Actor Model
- Scala - Akka - Actor model library for Scala based on Erlan's Actor model implementation.
- Testing
- Spec2 - "Software specification for Scala"
- ScalaCheck - Propery-based testing for Scala based on Haskell's QuickCheck.
- RPC - Remote Procedure Call
- Finagle - "Finagle is an extensible RPC system for the JVM, used to construct high-concurrency servers. Finagle implements uniform client and server APIs for several protocols, and is designed for high performance and concurrency. Most of Finagle’s code is protocol agnostic, simplifying the implementation of new protocols."
- Web Framework - Libraries or Frameworks to build Web Sites, Web applications or Web Servers.
- Math
- Spire - "Powerful new number types and numeric abstractions for Scala." -
See also:
- Awesome Scala - "A community driven list of useful Scala libraries, frameworks and software. This is not a catalog of all the libraries, just a starting point for your explorations."
2.3.2 Java Libraries
Selection of useful Java third party libraries.
- Parsers
- HTML Parser
- Json parser and serialization
- Http Client
- Date and Time
- Joda-Time - Facilities for date and time manipulation. It is already incorporated in Java 8.
- Utilities
- Apache Commons - Enhancements over Java Collections.
- Apache IO - Library to perform stream and file operations.
- Logging
- Testing
- Chart
- JfreeChart - Chart library to plot bar chart, line chart and so on.
- Math
- Apache Math - "Commons Math is a library of lightweight, self-contained mathematics and statistics components addressing the most common problems not available in the Java programming language or Commons Lang"
- JAMA - Java Matrix Package for linear algebra -
See also:
2.3.3 Sites to find Libraries
- https://search.maven.org/
- https://mvnrepository.com/
- Site for automated download of Java or Scala libraries.
- http://repo1.maven.org/
- Java Library - commons-io/commons-io/2.5
- Scalaz
- http://repo1.maven.org/maven2/org/scalaz/scalaz-core_2.12/
- http://repo1.maven.org/maven2/org/scalaz/scalaz-core_2.12/maven-metadata.xml
- http://repo1.maven.org/maven2/org/scalaz/scalaz-core_2.12/7.2.9/scalaz-core_2.12-7.2.9.pom
- http://repo1.maven.org/maven2/org/scalaz/scalaz-core_2.12/7.2.9/scalaz-core_2.12-7.2.9.jar
2.4 Community
2.5 Reports
- Refactoring a Complex GUI Application: A Case Study with the Auckland Layout Editor - https://www.cs.auckland.ac.nz/~lutteroth/publications/theses/ALE-IreneZhang-2014.pdf
- How Scala Improved Our Java http://spot.colorado.edu/~reids/talks/how-scala-improved-our-java.pdf
- Scala in practice https://www.slideshare.net/holograph/scala-in-practice-12803578
- 6 Months with Scala - An Experience Report https://jtcwang.me/scala-after-6-months
2.6 Presentations
- Martin Odersky. A Scalable Language https://www.slideshare.net/Odersky/fosdem-2009-1013261?qid=1f420201-9524-4f9f-9d9f-20c8cde0c52e&v=&b=&from_search=8
- Scala - just good for java shops? https://www.slideshare.net/snim2/scala-just-goodforjavashops