{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Lecture 2: Introdution to the Scala 2 Programming Language" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Originally by Sriram Sankaranarayanan \n", "\n", "Modified by Ravi Mangal \n", "\n", "Last Modified: Jan 27, 2025.\n", "\n", "Scala (stands for *scalable language*) is a highly scalable modern programming language designed by Martin Odersky and his coworkers at EPFL in Switzerland. It is built on top of the Java Virtual Machine (JVM) and is interoperable with Java. We will refer you to the Scala textbook for an introduction to Scala. This lecture will introduce important features of the language. We will point you out to tutorials on how to get yourself setup to program in Scala.\n", "\n", "Scala supports two modes of operation: as a scripting language, we can type Scala expressions on an interpreter (or equivalently a jupyter notebook that runs an interpreter in the background). This is good for learning the language and writing scripts. A larger standalone application will require you to compile Scala programs into Java bytecode, that is executed much the same way a Java program is executed.\n", "\n", "Scala has numerous attractive inbuilt features that make it ideal for implementing a wide variety of application. It has gained widespread popular adoption in the industry: many companies including Twitter, GitHub, LinkedIn, FourSquare and Netflix develop in Scala. More information about the language, its history and the community that surrounds it is available online at the Scala page: [https://www.scala-lang.org/]" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Hello World!!!\n" ] } ], "source": [ "/* In an interpreter you can write a command and just have it executed */\n", "println(\"Hello World!!!\")" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "defined \u001b[32mfunction\u001b[39m \u001b[36msayHelloTo\u001b[39m" ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "/* Let us define a function sayHelloTo \n", " that takes in an argument who which is a String.\n", " The println() function is similar to Java and is inbuilt into scala */\n", "def sayHelloTo(who: String) = { \n", " println(\"Hello, \"+ who)\n", "}" ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "scrolled": true }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Hello, Sriram\n", "Hello, Mary\n", "Hello, Alfred\n", "Hello, Mortimer\n", "Hello, Lilibeth\n", "Hello, Albert\n" ] } ], "source": [ "sayHelloTo(\"Sriram\")\n", "sayHelloTo(\"Mary\")\n", "sayHelloTo(\"Alfred\")\n", "sayHelloTo(\"Mortimer\")\n", "sayHelloTo(\"Lilibeth\")\n", "sayHelloTo(\"Albert\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 1. Writing a Standalone Program\n", "\n", "Although writing a function `sayHelloTo` works inside an interpreter, if we desire a compiled file, Scala (like Java) requires us to place methods inside of classes. \n", "\n", "__Side Remark:__ See a detailed explanation of why this is so for Java and Scala: [https://softwareengineering.stackexchange.com/questions/185109/why-java-does-not-allow-function-definitions-to-be-present-outside-of-the-class]\n", "\n", "Furthermore the entry point of the program must be a function called `main`. We write these as follows in Scala:\n", "\n", "---\n", "```\n", "object SayHelloTo {\n", "\n", " /* The function sayHelloTo is placed inside an \"object\" called SayHelloTo */\n", " def sayHelloTo(who: String) = { \n", " println(\"Hello, \"+ who)\n", " }\n", "\n", " /* This is the main function that is the entry point. \n", " args is an array of command line arguments as strings.\n", " */\n", " def main(args: Array[String]) = {\n", " // Iterate through the arguments one by one and call sayHelloTo on each\n", " for (name <- args) { sayHelloTo(name) }\n", " }\n", " \n", "}\n", "```\n", "---\n", "Cut and paste the code above into a file called `SayHelloTo.scala`. To compile this file: \n", "\n", "```\n", "$ scalac ./SayHelloTo.scala\n", "```\n", "\n", "This is the hard way to compile. There is a alternate tool called `sbt` that could be used for the purpose.\n", "To run this file:\n", "\n", "```\n", "$ scala SayHelloTo Sriram Albert Jane Mary Mortimer Lilibeth\n", "Hello, Sriram\n", "Hello, Albert\n", "Hello, Jane\n", "Hello, Mary\n", "Hello, Mortimer\n", "Hello, Lilibeth\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 2. Variable declarations (val and var)\n", "\n", "### Val (Immutable) \n", "\n", "Scala allows us to assign values to variables using the *val* declaration. However, *val* declarations do not produce a mutable variable like we are used to in Python or C++. Rather, *val* simply binds a name to a value. Let us write some examples to see this in action." ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "\u001b[36mx\u001b[39m: \u001b[32mInt\u001b[39m = \u001b[32m2\u001b[39m\n", "\u001b[36my\u001b[39m: \u001b[32mString\u001b[39m = \u001b[32m\"Hello\"\u001b[39m\n", "\u001b[36mz\u001b[39m: \u001b[32mString\u001b[39m = \u001b[32m\"World\"\u001b[39m" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "val x = 2\n", "val y = \"Hello\"\n", "val z = \"World\"" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "x = 2\n", "y = Hello\n", "z = World" ] } ], "source": [ "println(\"x = \"+ x)\n", "println(\"y = %s\" format (y) ) // This is almost same as format strings in Python\n", "print(s\"z = $z\") // This is a very useful \"macro\" in scala \n", " // You need to put an s in front of the string to inform scala.\n", " // The $z is replaced by the value of the variable z when printing" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "cmd6.sc:7: reassignment to val\n", "y = \"Hi\"\n", " ^\n", "cmd6.sc:8: reassignment to val\n", "val res6_1 = z = \"Donkey\"\n", " ^\n", "Compilation Failed" ] } ], "source": [ "/* Let us try changing a val to a new one. You will get an error that says \n", " \"reassignment to val\".\n", "\n", " This means that a val once assigned during its declaration cannot be\n", " reassigned to a new value.\n", "*/\n", "y = \"Hi\"\n", "z = \"Donkey\"" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Var (Mutables)\n", "\n", "If you wish the ability to reassign, you will need to declare the variable as a _var_ rather than a _val_." ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "
xx: Int = 25\n",
       "yy: String = "Hi"\n",
       "zz: String = "Donkey"
\n", "
" ], "text/plain": [ "\u001b[36mxx\u001b[39m: \u001b[32mInt\u001b[39m = \u001b[32m25\u001b[39m\n", "\u001b[36myy\u001b[39m: \u001b[32mString\u001b[39m = \u001b[32m\"Hi\"\u001b[39m\n", "\u001b[36mzz\u001b[39m: \u001b[32mString\u001b[39m = \u001b[32m\"Donkey\"\u001b[39m" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "var xx = 20\n", "var yy = \"Hello\"\n", "var zz = \"World\"" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "--> xx = 20, yy = Hello, zz = World <--\n" ] } ], "source": [ "println(\"--> xx = %d, yy = %s, zz = %s <--\" format (xx, yy, zz))" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [], "source": [ "/* Unlike val, we can reassign a var */\n", "xx = 25\n", "yy = \"Hi\"\n", "zz = \"Donkey\"" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "--> xx = 25, yy = Hi, zz = Donkey <--\n" ] } ], "source": [ "println(\"--> xx = %d, yy = %s, zz = %s <--\" format (xx, yy, zz))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Understanding: val vs var\n", "\n", "If you are a C/C++ programmer, the idea of using a _val_ may be familiar to you as something akin to a `const` declaration. Similarly, a _val_ is similar to a _final_ variable in Java. However, in *Scala*, the prefered style is to use _val_ to declare variables whenever possible, and use _var_ only if it is truly unavoidable.\n", "\n", "There are many reasons why we would prefer _val_. The primary reason is that once a value is immutable, it can be shared between objects. As an example, we can declare a string and repeat it 100 times." ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "\u001b[36mname\u001b[39m: \u001b[32mString\u001b[39m = \u001b[32m\"Einstein\"\u001b[39m\n", "\u001b[36mlist_name\u001b[39m: \u001b[32mList\u001b[39m[\u001b[32mString\u001b[39m] = \u001b[33mList\u001b[39m(\n", " \u001b[32m\"Einstein\"\u001b[39m,\n", " \u001b[32m\"Einstein\"\u001b[39m,\n", " \u001b[32m\"Einstein\"\u001b[39m,\n", " \u001b[32m\"Einstein\"\u001b[39m,\n", " \u001b[32m\"Einstein\"\u001b[39m,\n", " \u001b[32m\"Einstein\"\u001b[39m,\n", " \u001b[32m\"Einstein\"\u001b[39m,\n", " \u001b[32m\"Einstein\"\u001b[39m,\n", " \u001b[32m\"Einstein\"\u001b[39m,\n", " \u001b[32m\"Einstein\"\u001b[39m,\n", " \u001b[32m\"Einstein\"\u001b[39m,\n", " \u001b[32m\"Einstein\"\u001b[39m\n", ")" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ "val name = \"Einstein\"\n", "// Make a list with the name of a famous person\n", "\n", "// The internal implementation can keep one copy of the name and share a reference \n", "// 12 times. This can save a lot of space and is only possible because we guarantee that\n", "// the string is immutable.\n", "val list_name = List(name, name, name, name, name, name, name, name, \n", " name, name, name, name)" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "\u001b[36mname2\u001b[39m: \u001b[32mString\u001b[39m = \u001b[32m\"Salam\"\u001b[39m\n", "\u001b[36mlist_name2\u001b[39m: \u001b[32mList\u001b[39m[\u001b[32mString\u001b[39m] = \u001b[33mList\u001b[39m(\n", " \u001b[32m\"Feynman\"\u001b[39m,\n", " \u001b[32m\"Feynman\"\u001b[39m,\n", " \u001b[32m\"Feynman\"\u001b[39m,\n", " \u001b[32m\"Feynman\"\u001b[39m,\n", " \u001b[32m\"Feynman\"\u001b[39m,\n", " \u001b[32m\"Feynman\"\u001b[39m,\n", " \u001b[32m\"Feynman\"\u001b[39m,\n", " \u001b[32m\"Feynman\"\u001b[39m,\n", " \u001b[32m\"Feynman\"\u001b[39m,\n", " \u001b[32m\"Feynman\"\u001b[39m,\n", " \u001b[32m\"Feynman\"\u001b[39m\n", ")" ] }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "// However if I define something as a var\n", "var name2 = \"Feynman\"\n", "// If I start to place copies of name2, scala recognizes that name2 is mutable \n", "// and is forced to copy over the contents of the name2 each time.\n", "val list_name2 = List(name2, name2, name2, name2, name2, name2, name2, name2, \n", " name2, name2, name2)\n", "name2 = \"Salam\"" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Hello\n" ] }, { "data": { "text/html": [ "
\n", "
x: String = "World"\n",
       "y: String = "Hello"
\n", "
" ], "text/plain": [ "\u001b[36mx\u001b[39m: \u001b[32mString\u001b[39m = \u001b[32m\"World\"\u001b[39m\n", "\u001b[36my\u001b[39m: \u001b[32mString\u001b[39m = \u001b[32m\"Hello\"\u001b[39m" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "var x = \"Hello\"// Declare x\n", "val y = x // Copy x into val y\n", "x = \"World\" // Mutate x\n", "println(y) // y remains unchanged" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The other reasons are that a _val_ being immutable permits the Scala compiler to enable important compiler optimizations such as constant folding or code motion, which may not be otherwise possible. If you are unsure\n", "what these are, feel free to ask for a more detailed explanation.\n", "\n", "__We will avoid using var and strongly prefer val, as long as it is possible to do so__" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 3. Classes in Scala\n", "\n", "Scala is object oriented language, and therefore classes are a fundamental means of encapsulating code and data inside a single convenient structure. Scala classes are very easy to define and we can even avoid a lot of unnecessary code that is needed in the form of constructors, destructors and so on in Scala." ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "defined \u001b[32mclass\u001b[39m \u001b[36mDog\u001b[39m" ] }, "execution_count": 13, "metadata": {}, "output_type": "execute_result" } ], "source": [ "/* Let us define a class called Dog which is defined by name, breed and age */\n", "\n", "\n", "class Dog(val name: String, val breed: String, val age: Int)\n", "/* name, breed and age are called class parameters. \n", " They are defined as val. This means that we cannot modify them. */\n", "\n", "// The class has no other contents." ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "\u001b[36md1\u001b[39m: \u001b[32mDog\u001b[39m = ammonite.$sess.cmd13$Helper$Dog@6639e52e\n", "\u001b[36md2\u001b[39m: \u001b[32mDog\u001b[39m = ammonite.$sess.cmd13$Helper$Dog@33809bd5\n", "\u001b[36md1_name\u001b[39m: \u001b[32mString\u001b[39m = \u001b[32m\"Samuel\"\u001b[39m\n", "\u001b[36md1_age\u001b[39m: \u001b[32mInt\u001b[39m = \u001b[32m11\u001b[39m\n", "\u001b[36md1_breed\u001b[39m: \u001b[32mString\u001b[39m = \u001b[32m\"Alsatian\"\u001b[39m\n", "\u001b[36md2_name\u001b[39m: \u001b[32mString\u001b[39m = \u001b[32m\"Bo\"\u001b[39m\n", "\u001b[36md2_age\u001b[39m: \u001b[32mInt\u001b[39m = \u001b[32m10\u001b[39m" ] }, "execution_count": 14, "metadata": {}, "output_type": "execute_result" } ], "source": [ "val d1 = new Dog(\"Samuel\", \"Alsatian\", 11)\n", "val d2 = new Dog(\"Bo\", \"Portuguese water dog\", 10)\n", "/* Notice how name, age and breed are field names of d1 and d2 */\n", "val d1_name = d1.name \n", "val d1_age = d1.age \n", "val d1_breed = d1.breed\n", "val d2_name = d2.name\n", "val d2_age = d2.age" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Dog d1's name is Samuel. It is 11 years old and is a proud Alsatian\n" ] } ], "source": [ "println(\"Dog d1's name is %s. It is %d years old and is a proud %s\" \n", " format (d1.name, d1.age, d1.breed ))" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "cmd16.sc:2: reassignment to val\n", "d1.name = \"Donkey\" /* This will give you the familiar reassignment to val error */\n", " ^\n", "Compilation Failed" ] } ], "source": [ "/* Because the field names are defined as val, we cannot mutate them */\n", "d1.name = \"Donkey\" /* This will give you the familiar reassignment to val error */" ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "defined \u001b[32mclass\u001b[39m \u001b[36mCat\u001b[39m" ] }, "execution_count": 16, "metadata": {}, "output_type": "execute_result" } ], "source": [ "/* Let us now define a cat with class parameters name and breed that are \n", " immutable but numLives a mutable */\n", "class Cat(val name: String, val breed: String, var numLives: Int){\n", " /* toString() is a function that is defined by default for all objects.\n", " We can redefine toString() but we need to say override to tell Scala\n", " that we are redefining it. */\n", " override def toString(): String = {\n", " return \"Cat: \"+ name + \" of breed \" + breed + \" with \" + numLives + \" lives.\"\n", " }\n", " \n", "}" ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "\u001b[36mc\u001b[39m: \u001b[32mCat\u001b[39m = Cat: Jenkins of breed Siamese with 2 lives." ] }, "execution_count": 17, "metadata": {}, "output_type": "execute_result" } ], "source": [ "val c = new Cat(\"Jenkins\", \"Siamese\", 2)" ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Before curiosity: 2 lives\n", "After curiosity: 1 lives\n" ] } ], "source": [ "println(s\"Before curiosity: ${c.numLives} lives\")\n", "/* Oops! Curiosity killed the cat */\n", "c.numLives -= 1\n", "println(s\"After curiosity: ${c.numLives} lives\")" ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "cmd19.sc:2: reassignment to val\n", "c.breed = \"Persian\"\n", " ^\n", "Compilation Failed" ] } ], "source": [ "/* However, attempting to change the name and breed of a cat will lead to an error. Can you guess which one? */\n", "c.breed = \"Persian\"" ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "defined \u001b[32mclass\u001b[39m \u001b[36mBarkingDog\u001b[39m" ] }, "execution_count": 19, "metadata": {}, "output_type": "execute_result" } ], "source": [ "/* Classes can have methods inside them */\n", "class BarkingDog(val name: String, val breed:String = \"Alsatian\", val age: Int = 20 ) {\n", " /* A list of shouts for the dog */\n", " val listOfShouts = List(\"Wuff Wuff\", \"Bow Wow Wow\", \"ARuff ARuff\", \"Rwoff\", \"Meow\")\n", " /* The current shout */\n", " private var idx = 0 \n", " def bark() = {\n", " /* Print the bark */\n", " println(s\"${this.name}, a proud ${this.breed} says ${listOfShouts(idx)}!\")\n", " /* Increment the index */\n", " this.idx = (this.idx + 1) \n", " if (this.idx >= listOfShouts.length) { /* Wrap around to zero */\n", " this.idx = 0\n", " }\n", " } \n", "}\n" ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "\u001b[36md\u001b[39m: \u001b[32mBarkingDog\u001b[39m = ammonite.$sess.cmd19$Helper$BarkingDog@6b1368b9\n", "\u001b[36md1\u001b[39m: \u001b[32mBarkingDog\u001b[39m = ammonite.$sess.cmd19$Helper$BarkingDog@5ba96f75\n", "\u001b[36mres20_2\u001b[39m: \u001b[32mString\u001b[39m = \u001b[32m\"Alsatian\"\u001b[39m\n", "\u001b[36mres20_3\u001b[39m: \u001b[32mInt\u001b[39m = \u001b[32m20\u001b[39m" ] }, "execution_count": 20, "metadata": {}, "output_type": "execute_result" } ], "source": [ "val d = new BarkingDog(\"Bo\", \"Burmese Mountain Dog\", 3)\n", "val d1 = new BarkingDog(\"Ciao\")\n", "d1.breed\n", "d1.age" ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "1: Bo, a proud Burmese Mountain Dog says Wuff Wuff!\n", "2: Bo, a proud Burmese Mountain Dog says Bow Wow Wow!\n", "3: Bo, a proud Burmese Mountain Dog says ARuff ARuff!\n", "4: Bo, a proud Burmese Mountain Dog says Rwoff!\n", "5: Bo, a proud Burmese Mountain Dog says Meow!\n", "6: Bo, a proud Burmese Mountain Dog says Wuff Wuff!\n", "7: Bo, a proud Burmese Mountain Dog says Bow Wow Wow!\n", "8: Bo, a proud Burmese Mountain Dog says ARuff ARuff!\n", "9: Bo, a proud Burmese Mountain Dog says Rwoff!\n", "10: Bo, a proud Burmese Mountain Dog says Meow!\n" ] } ], "source": [ "/* Bark 10 times */\n", "for (i <- 1 to 10) { print(s\"$i: \")\n", " d.bark() }" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We have taken a first look at Scala classes. We will look deeper into some concepts involving Scala classes later on as the course progresses. However, if you want to stay ahead, you are welcome to take a look now.\n", " - Classes with multiple fields, constructors and inheritence (the standard stuff).\n", " - Abstract classes and Traits.\n", " - Case classes\n", " - Companion classes " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 4. Organizing Data\n", "\n", "Scala supports basic data types of integers, booleans, strings, floating point plus important data structures for storing data starting from Lists, Tuples, Arrays, Sequences, Sets, Maps and an entire standard library of routines to manipulate them just like in Java. In fact, it is quiet easy to instantiate a Java objects including Java standard libraries inside Scala, and run Java code.\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Basic Data Types: Integers, Booleans, Strings, Floats and ...\n", "\n", "We will now examine basic data types in Scala." ] }, { "cell_type": "code", "execution_count": 22, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "\u001b[36mx\u001b[39m: \u001b[32mInt\u001b[39m = \u001b[32m23\u001b[39m\n", "\u001b[36my\u001b[39m: \u001b[32mInt\u001b[39m = \u001b[32m35\u001b[39m\n", "\u001b[36mz\u001b[39m: \u001b[32mInt\u001b[39m = \u001b[32m58\u001b[39m" ] }, "execution_count": 22, "metadata": {}, "output_type": "execute_result" } ], "source": [ "val x: Int = 23 // Int is supposed to be 32 bits\n", "val y: Int = 35\n", "val z: Int = x + y" ] }, { "cell_type": "code", "execution_count": 23, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "\u001b[36mx1\u001b[39m: \u001b[32mLong\u001b[39m = \u001b[32m23L\u001b[39m\n", "\u001b[36my1\u001b[39m: \u001b[32mInt\u001b[39m = \u001b[32m35\u001b[39m\n", "\u001b[36mz1\u001b[39m: \u001b[32mLong\u001b[39m = \u001b[32m58L\u001b[39m\n", "\u001b[36mz2\u001b[39m: \u001b[32mLong\u001b[39m = \u001b[32m58L\u001b[39m\n", "\u001b[36mz3\u001b[39m: \u001b[32mInt\u001b[39m = \u001b[32m58\u001b[39m" ] }, "execution_count": 23, "metadata": {}, "output_type": "execute_result" } ], "source": [ "val x1: Long = 23L // Long is 64 bit \n", "val y1: Int = 35 \n", "val z1 = x1 + y1 // The type of y1 is automatically promoted to Long and result is Long\n", "val z2 = x1 + (y1) // convert y1 to long\n", "val z3: Int = (x1 + y1).toInt" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "__Important Fact about Scala:__ All values are actually classes. `Int`, `Long`, `Float`, `Double`, `String`, `Boolean` are all classes. The addition ` val z = x + y ` is simply shorthand for `val z = x.+ (y)`. This means that we can overload and define new operators quite easily in Scala (but more on that later).\n", "\n", "Read the documentation of the [`Int` class](https://www.scala-lang.org/api/2.13.16/scala/Int.html) and [`Long` class](https://www.scala-lang.org/api/2.13.16/scala/Long.html) in Scala. Look up methods like `toInt`, `toLong`, `toString`." ] }, { "cell_type": "code", "execution_count": 24, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "\u001b[36mz\u001b[39m: \u001b[32mInt\u001b[39m = \u001b[32m58\u001b[39m" ] }, "execution_count": 24, "metadata": {}, "output_type": "execute_result" } ], "source": [ "val z = x.+(y)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Booleans\n", "Scala supports boolean types with the and (`&&`) or (`||`) and (`!`) operators." ] }, { "cell_type": "code", "execution_count": 25, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "\u001b[36mb1\u001b[39m: \u001b[32mBoolean\u001b[39m = \u001b[32mtrue\u001b[39m\n", "\u001b[36mb2\u001b[39m: \u001b[32mBoolean\u001b[39m = \u001b[32mfalse\u001b[39m\n", "\u001b[36mb3\u001b[39m: \u001b[32mBoolean\u001b[39m = \u001b[32mfalse\u001b[39m\n", "\u001b[36mb4\u001b[39m: \u001b[32mBoolean\u001b[39m = \u001b[32mtrue\u001b[39m\n", "\u001b[36mb5\u001b[39m: \u001b[32mBoolean\u001b[39m = \u001b[32mtrue\u001b[39m" ] }, "execution_count": 25, "metadata": {}, "output_type": "execute_result" } ], "source": [ "val b1: Boolean = true\n", "val b2 = !b1 // Not operator\n", "val b3 = b1 && b2 // And operator\n", "val b4 = b1 || b3 // Or operator\n", "val b5: Boolean = if (b4) b1 else b3 // If then else\n", "// We have written up type annotations for some of the vals and\n", "// not for the others, can you remove them and see what happens?\n", "// Answer : the type annotations so far are optional because of a powerful \n", "// type inference system in scala.\n", "// We will learn about type checking and type inference in this class." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Tuples\n", "\n", "A [tuple](https://www.scala-lang.org/api/2.13.16/scala/Long.html?search=tuple) is a collection of values. Tuples can be *pairs*, *triples*, *quadruples* and so on. Using more than five or six elements in a tuple is not advisable: we can use a list or an object in those cases. Tuples are often used in functions to return multiple values or in general to keep a small number of associated values close together (it often may not make sense to define a separate object for the same).\n" ] }, { "cell_type": "code", "execution_count": 26, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "\u001b[36mv\u001b[39m: (\u001b[32mInt\u001b[39m, \u001b[32mBoolean\u001b[39m) = (\u001b[32m23\u001b[39m, \u001b[32mtrue\u001b[39m)" ] }, "execution_count": 26, "metadata": {}, "output_type": "execute_result" } ], "source": [ "val v : (Int, Boolean) = (x, b1) // A tuple of integer and boolean" ] }, { "cell_type": "code", "execution_count": 27, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "\u001b[36mx1\u001b[39m: \u001b[32mInt\u001b[39m = \u001b[32m23\u001b[39m\n", "\u001b[36mx2\u001b[39m: \u001b[32mBoolean\u001b[39m = \u001b[32mtrue\u001b[39m\n", "\u001b[36mvn\u001b[39m: \u001b[32mInt\u001b[39m = \u001b[32m2\u001b[39m\n", "\u001b[36mvSwap\u001b[39m: (\u001b[32mBoolean\u001b[39m, \u001b[32mInt\u001b[39m) = (\u001b[32mtrue\u001b[39m, \u001b[32m23\u001b[39m)\n", "\u001b[36mv3\u001b[39m: (\u001b[32mInt\u001b[39m, \u001b[32mString\u001b[39m, \u001b[32mString\u001b[39m, \u001b[32mDouble\u001b[39m) = (\u001b[32m24\u001b[39m, \u001b[32m\"Hello\"\u001b[39m, \u001b[32m\"World\"\u001b[39m, \u001b[32m2.5\u001b[39m)\n", "\u001b[36mx31\u001b[39m: \u001b[32mInt\u001b[39m = \u001b[32m24\u001b[39m\n", "\u001b[36mx32\u001b[39m: \u001b[32mString\u001b[39m = \u001b[32m\"Hello\"\u001b[39m\n", "\u001b[36mx33\u001b[39m: \u001b[32mString\u001b[39m = \u001b[32m\"World\"\u001b[39m\n", "\u001b[36mx34\u001b[39m: \u001b[32mDouble\u001b[39m = \u001b[32m2.5\u001b[39m" ] }, "execution_count": 27, "metadata": {}, "output_type": "execute_result" } ], "source": [ "// You can access the components of a tuple using the _1, _2, and so on\n", "val x1: Int = v._1\n", "val x2: Boolean = v._2\n", "val vn = v.productArity // This is how you get the size of a tuple\n", "val vSwap = v.swap // Interchange the order of the tuple\n", "\n", "val v3 = (24, \"Hello\", \"World\", 2.5) // 4-tuple of a Int,String,String,Double\n", "val x31 = v3._1 // You can retrieve the components of a tuple\n", "val x32 = v3._2 \n", "val x33 = v3._3\n", "val x34 = v3._4" ] }, { "cell_type": "code", "execution_count": 28, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Unpacking v3:\n", "24\n", "Hello\n", "World\n", "2.5\n" ] } ], "source": [ "// You can iterate through the contents of a tuple as follows\n", "println(\"Unpacking v3:\")\n", "for (i <- v3.productIterator) { \n", " println(i)\n", "}\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Lists\n", "While, in principle, you can have tuples of larger and larger sizes, it is not practical to have tuples of more than 10 elements. Tuples have a fixed size: we cannot add or remove entries from a tuple. Also, it is cumbersome to iterate through a tuple. For applications that require containers that can grow or shrink, we use [Lists](https://www.scala-lang.org/api/2.13.16/scala/collection/immutable/List.html) (immutable) or [Arrays](https://www.scala-lang.org/api/2.13.16/scala/Array.html) (mutable).\n" ] }, { "cell_type": "code", "execution_count": 29, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Caught IndexOutOfBoundsException" ] }, { "data": { "text/plain": [ "\u001b[36mx\u001b[39m: \u001b[32mList\u001b[39m[\u001b[32mInt\u001b[39m] = \u001b[33mList\u001b[39m(\u001b[32m1\u001b[39m, \u001b[32m2\u001b[39m, \u001b[32m3\u001b[39m)\n", "\u001b[36mx_size\u001b[39m: \u001b[32mInt\u001b[39m = \u001b[32m3\u001b[39m\n", "\u001b[36melt0\u001b[39m: \u001b[32mInt\u001b[39m = \u001b[32m1\u001b[39m\n", "\u001b[36melt1\u001b[39m: \u001b[32mInt\u001b[39m = \u001b[32m2\u001b[39m\n", "\u001b[36melt2\u001b[39m: \u001b[32mInt\u001b[39m = \u001b[32m3\u001b[39m\n", "\u001b[36melt3\u001b[39m: \u001b[32mInt\u001b[39m = \u001b[32m-100000\u001b[39m" ] }, "execution_count": 29, "metadata": {}, "output_type": "execute_result" } ], "source": [ "val x : List[Int] = List(1, 2, 3) // A list of integers\n", "val x_size = x.size\n", "val elt0 = x(0)\n", "val elt1 = x(1)\n", "val elt2 = x(2)\n", "val elt3 = try {\n", " x(3) // This is going to throw an exception\n", "} catch {\n", " case ex: IndexOutOfBoundsException => {print(\"Caught IndexOutOfBoundsException\"); -100000}\n", "}" ] }, { "cell_type": "code", "execution_count": 30, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "\u001b[36my\u001b[39m: \u001b[32mList\u001b[39m[\u001b[32mAny\u001b[39m] = \u001b[33mList\u001b[39m(\u001b[32m1\u001b[39m, \u001b[32m2\u001b[39m, \u001b[32m\"Hello\"\u001b[39m, \u001b[32m\"World\"\u001b[39m, \u001b[32m5.0\u001b[39m)" ] }, "execution_count": 30, "metadata": {}, "output_type": "execute_result" } ], "source": [ "val y = List(1, 2, \"Hello\", \"World\", 5.0)" ] }, { "cell_type": "code", "execution_count": 30, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "cmd31.sc:2: value update is not a member of List[Int]\n", "did you mean updated?\n", "x(2) = 10 // You cannot change the element of a list.\n", "^\n", "Compilation Failed" ] } ], "source": [ "// Lists are immutable\n", "x(2) = 10 // You cannot change the element of a list." ] }, { "cell_type": "code", "execution_count": 31, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "\u001b[36mx_changed\u001b[39m: \u001b[32mList\u001b[39m[\u001b[32mInt\u001b[39m] = \u001b[33mList\u001b[39m(\u001b[32m1\u001b[39m, \u001b[32m2\u001b[39m, \u001b[32m10\u001b[39m)" ] }, "execution_count": 31, "metadata": {}, "output_type": "execute_result" } ], "source": [ "// Instead you can get a new list by using the updated function\n", "// This gives us a fresh list that copies x into x_changed and replaces x_changed(2) by 10\n", "val x_changed = x.updated(2,10) \n" ] }, { "cell_type": "code", "execution_count": 32, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "1\n", "2\n", "Hello\n", "World\n", "5.0\n" ] } ], "source": [ "//You can iterate through a list very easily\n", "for (elt <- y){\n", " println(elt)\n", "}" ] }, { "cell_type": "code", "execution_count": 33, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "\u001b[36mz\u001b[39m: \u001b[32mList\u001b[39m[\u001b[32mAny\u001b[39m] = \u001b[33mList\u001b[39m(\u001b[32m\"AppendMeToFront\"\u001b[39m, \u001b[32m1\u001b[39m, \u001b[32m2\u001b[39m, \u001b[32m\"Hello\"\u001b[39m, \u001b[32m\"World\"\u001b[39m, \u001b[32m5.0\u001b[39m)\n", "\u001b[36mzz\u001b[39m: \u001b[32mList\u001b[39m[\u001b[32mAny\u001b[39m] = \u001b[33mList\u001b[39m(\n", " \u001b[32m\"AppendMeToFront\"\u001b[39m,\n", " \u001b[32m1\u001b[39m,\n", " \u001b[32m2\u001b[39m,\n", " \u001b[32m\"Hello\"\u001b[39m,\n", " \u001b[32m\"World\"\u001b[39m,\n", " \u001b[32m5.0\u001b[39m,\n", " \u001b[32m\"AppendMeToFront\"\u001b[39m,\n", " \u001b[32m1\u001b[39m,\n", " \u001b[32m2\u001b[39m,\n", " \u001b[32m\"Hello\"\u001b[39m,\n", " \u001b[32m\"World\"\u001b[39m,\n", " \u001b[32m5.0\u001b[39m\n", ")\n", "\u001b[36mzz1\u001b[39m: \u001b[32mList\u001b[39m[\u001b[32mAny\u001b[39m] = \u001b[33mList\u001b[39m(\n", " \u001b[32m\"AppendMeToFront\"\u001b[39m,\n", " \u001b[32m\"World\"\u001b[39m,\n", " \u001b[32m5.0\u001b[39m,\n", " \u001b[32m\"AppendMeToFront\"\u001b[39m,\n", " \u001b[32m1\u001b[39m,\n", " \u001b[32m2\u001b[39m,\n", " \u001b[32m\"Hello\"\u001b[39m,\n", " \u001b[32m\"World\"\u001b[39m,\n", " \u001b[32m5.0\u001b[39m\n", ")\n", "\u001b[36mbb4\u001b[39m: \u001b[32mBoolean\u001b[39m = \u001b[32mtrue\u001b[39m\n", "defined \u001b[32mfunction\u001b[39m \u001b[36mremoveAllOccurrences\u001b[39m\n", "\u001b[36mzz3\u001b[39m: \u001b[32mList\u001b[39m[\u001b[32mAny\u001b[39m] = \u001b[33mList\u001b[39m(\n", " \u001b[32m\"AppendMeToFront\"\u001b[39m,\n", " \u001b[32m\"World\"\u001b[39m,\n", " \u001b[32m5.0\u001b[39m,\n", " \u001b[32m\"AppendMeToFront\"\u001b[39m,\n", " \u001b[32m\"World\"\u001b[39m,\n", " \u001b[32m5.0\u001b[39m\n", ")" ] }, "execution_count": 33, "metadata": {}, "output_type": "execute_result" } ], "source": [ "// You can append an element to a list\n", "val z = \"AppendMeToFront\"::y\n", "// You can merge two lists using `:::` (note that `::` appends an element to the front, whereas `:::` appends two lists)\n", "val zz = z ::: z\n", "// You can remove elements of list b from a list a\n", "val zz1 = zz.diff(List(1,2,\"Hello\")) \n", "val bb4 = zz1.contains(\"Hello\")\n", "\n", "// Notice that diff removes the first occurrence of 1,2 and \"Hello\" but not all occurrences\n", "// To remove all occurences of list a from b, we have to use a filterNot operator\n", "def removeAllOccurrences(a: List[Any], b:List[Any]): List[Any] = {\n", " a.filterNot( v => ( b.contains(v) )) // v => b.contains(v) is called an anonymous function. We will study these soon.\n", "}\n", "\n", "val zz3 = removeAllOccurrences(zz, List(1,2,\"Hello\"))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### List API\n", "\n", "Scala has a rich API of operations for lists here : https://www.scala-lang.org/api/2.13.16/scala/collection/immutable/List.html\n", "\n", "As an exercise, you can read through this documentation to locate methods that can do the following:\n", "1. Find if an element belongs to a list\n", "2. Find all elements in a list which are multiples of 3\n", "3. Reverse a list\n", "4. Remove all duplicates from a list\n", "5. Sort a list\n", "\n", "## Arrays\n", "\n", "[Arrays](https://www.scala-lang.org/api/2.13.16/scala/Array.html) are mutable, unlike lists. I.e, you can change the value of each cell unlike lists. " ] }, { "cell_type": "code", "execution_count": 34, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "i --> a1(i)\n", "-----------\n", "0 --> 1\n", "1 --> 2\n", "2 --> 3\n", "3 --> 4\n", "4 --> 5\n" ] }, { "data": { "text/plain": [ "\u001b[36ma1\u001b[39m: \u001b[32mArray\u001b[39m[\u001b[32mInt\u001b[39m] = \u001b[33mArray\u001b[39m(\u001b[32m1\u001b[39m, \u001b[32m2\u001b[39m, \u001b[32m3\u001b[39m, \u001b[32m4\u001b[39m, \u001b[32m5\u001b[39m)" ] }, "execution_count": 34, "metadata": {}, "output_type": "execute_result" } ], "source": [ "val a1: Array[Int] = Array(1, 2, 3, 4, 5)\n", "println(\"i --> a1(i)\\n-----------\")\n", "for (i <- 0 until a1.size){ println(s\"$i --> ${a1(i)}\")}" ] }, { "cell_type": "code", "execution_count": 35, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "i --> a1(i)\n", "-----------\n", "0 --> 1\n", "1 --> 2\n", "2 --> 5\n", "3 --> 15\n", "4 --> 5\n" ] } ], "source": [ "a1(2) = 5\n", "a1(3) = 15\n", "println(\"i --> a1(i)\\n-----------\")\n", "for (i <- 0 until a1.size){ println(s\"$i --> ${a1(i)}\")}" ] }, { "cell_type": "code", "execution_count": 36, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "i --> a2(i)\n", "-----------\n", "0 --> 12\n", "1 --> 1\n", "2 --> 2\n", "3 --> 5\n", "4 --> 15\n", "5 --> 5\n", "\n", "i --> a3(i)\n", "-----------\n", "0 --> 1\n", "1 --> 2\n", "2 --> 5\n", "3 --> 15\n", "4 --> 5\n", "5 --> 1112\n" ] }, { "data": { "text/plain": [ "\u001b[36ma2\u001b[39m: \u001b[32mArray\u001b[39m[\u001b[32mInt\u001b[39m] = \u001b[33mArray\u001b[39m(\u001b[32m12\u001b[39m, \u001b[32m1\u001b[39m, \u001b[32m2\u001b[39m, \u001b[32m5\u001b[39m, \u001b[32m15\u001b[39m, \u001b[32m5\u001b[39m)\n", "\u001b[36ma3\u001b[39m: \u001b[32mArray\u001b[39m[\u001b[32mInt\u001b[39m] = \u001b[33mArray\u001b[39m(\u001b[32m1\u001b[39m, \u001b[32m2\u001b[39m, \u001b[32m5\u001b[39m, \u001b[32m15\u001b[39m, \u001b[32m5\u001b[39m, \u001b[32m1112\u001b[39m)" ] }, "execution_count": 36, "metadata": {}, "output_type": "execute_result" } ], "source": [ "// Most list operations are supported in arrays, as well\n", "\n", "val a2 = 12 +: a1 // Append 12 in front of a2\n", "val a3 = a1 :+ 1112 // Append 1112 to the back of array a1\n", "println(\"i --> a2(i)\\n-----------\")\n", "for (i <- 0 until a2.size){ println(s\"$i --> ${a2(i)}\")}\n", "println()\n", "println(\"i --> a3(i)\\n-----------\")\n", "for (i <- 0 until a3.size){ println(s\"$i --> ${a3(i)}\")}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Maps\n", "\n", "[Maps](https://www.scala-lang.org/api/2.13.16/scala/collection/Map.html) are a useful data structure for storing associations between keys and values. Scala supports both immutable and mutable maps. We will focus on immutable maps as much as possible." ] }, { "cell_type": "code", "execution_count": 37, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "\u001b[36mm0\u001b[39m: \u001b[32mMap\u001b[39m[\u001b[32mInt\u001b[39m, \u001b[32mString\u001b[39m] = \u001b[33mHashMap\u001b[39m(\n", " \u001b[32m5\u001b[39m -> \u001b[32m\"Odersky\"\u001b[39m,\n", " \u001b[32m1\u001b[39m -> \u001b[32m\"Hello\"\u001b[39m,\n", " \u001b[32m2\u001b[39m -> \u001b[32m\"World\"\u001b[39m,\n", " \u001b[32m3\u001b[39m -> \u001b[32m\"Scala\"\u001b[39m,\n", " \u001b[32m4\u001b[39m -> \u001b[32m\"Java\"\u001b[39m\n", ")" ] }, "execution_count": 37, "metadata": {}, "output_type": "execute_result" } ], "source": [ "val m0: Map[Int, String]= Map (1 -> \"Hello\", 2 -> \"World\", 3 -> \"Scala\", 4 -> \"Java\", 5 -> \"Odersky\" ) \n", "// It is easy to create a map" ] }, { "cell_type": "code", "execution_count": 38, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Scala\n" ] }, { "data": { "text/plain": [ "\u001b[36mb0\u001b[39m: \u001b[32mBoolean\u001b[39m = \u001b[32mtrue\u001b[39m\n", "\u001b[36mb1\u001b[39m: \u001b[32mBoolean\u001b[39m = \u001b[32mfalse\u001b[39m" ] }, "execution_count": 38, "metadata": {}, "output_type": "execute_result" } ], "source": [ "val b0 = m0.contains(3) // Does it have an entry for the key 3?\n", "val b1 = m0.contains(6) // Does it have an entry for the key 6?\n", "println(m0(3))\n" ] }, { "cell_type": "code", "execution_count": 39, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "\u001b[36mm1\u001b[39m: \u001b[32mMap\u001b[39m[\u001b[32mInt\u001b[39m, \u001b[32mString\u001b[39m] = \u001b[33mHashMap\u001b[39m(\n", " \u001b[32m5\u001b[39m -> \u001b[32m\"Odersky\"\u001b[39m,\n", " \u001b[32m1\u001b[39m -> \u001b[32m\"Hello\"\u001b[39m,\n", " \u001b[32m6\u001b[39m -> \u001b[32m\"Martin\"\u001b[39m,\n", " \u001b[32m2\u001b[39m -> \u001b[32m\"World\"\u001b[39m,\n", " \u001b[32m3\u001b[39m -> \u001b[32m\"Scala\"\u001b[39m,\n", " \u001b[32m4\u001b[39m -> \u001b[32m\"Java\"\u001b[39m\n", ")" ] }, "execution_count": 39, "metadata": {}, "output_type": "execute_result" } ], "source": [ "val m1 = m0 + (6 -> \"Martin\")" ] }, { "cell_type": "code", "execution_count": 40, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "\u001b[36mm2\u001b[39m: \u001b[32mMap\u001b[39m[\u001b[32mInt\u001b[39m, \u001b[32mString\u001b[39m] = \u001b[33mHashMap\u001b[39m(\n", " \u001b[32m5\u001b[39m -> \u001b[32m\"Odersky\"\u001b[39m,\n", " \u001b[32m1\u001b[39m -> \u001b[32m\"Hello\"\u001b[39m,\n", " \u001b[32m6\u001b[39m -> \u001b[32m\"Martin\"\u001b[39m,\n", " \u001b[32m9\u001b[39m -> \u001b[32m\"Object Oriented\"\u001b[39m,\n", " \u001b[32m2\u001b[39m -> \u001b[32m\"World\"\u001b[39m,\n", " \u001b[32m7\u001b[39m -> \u001b[32m\"Functional\"\u001b[39m,\n", " \u001b[32m3\u001b[39m -> \u001b[32m\"Scala\"\u001b[39m,\n", " \u001b[32m8\u001b[39m -> \u001b[32m\"Programming\"\u001b[39m,\n", " \u001b[32m4\u001b[39m -> \u001b[32m\"Java\"\u001b[39m\n", ")" ] }, "execution_count": 40, "metadata": {}, "output_type": "execute_result" } ], "source": [ "val m2 = m1 ++ List( (7 -> \"Functional\"), (8 -> \"Programming\"), (9 -> \"Object Oriented\")) // Use ++ to append multiple entries at once.\n" ] }, { "cell_type": "code", "execution_count": 41, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "\u001b[36mm5\u001b[39m: \u001b[32mMap\u001b[39m[\u001b[32mInt\u001b[39m, \u001b[32mString\u001b[39m] = \u001b[33mHashMap\u001b[39m(\n", " \u001b[32m5\u001b[39m -> \u001b[32m\"Odersky\"\u001b[39m,\n", " \u001b[32m1\u001b[39m -> \u001b[32m\"Hello\"\u001b[39m,\n", " \u001b[32m6\u001b[39m -> \u001b[32m\"Michael\"\u001b[39m,\n", " \u001b[32m9\u001b[39m -> \u001b[32m\"Object Oriented\"\u001b[39m,\n", " \u001b[32m2\u001b[39m -> \u001b[32m\"World\"\u001b[39m,\n", " \u001b[32m7\u001b[39m -> \u001b[32m\"Functional\"\u001b[39m,\n", " \u001b[32m3\u001b[39m -> \u001b[32m\"Scala\"\u001b[39m,\n", " \u001b[32m8\u001b[39m -> \u001b[32m\"Programming\"\u001b[39m,\n", " \u001b[32m4\u001b[39m -> \u001b[32m\"Java\"\u001b[39m\n", ")" ] }, "execution_count": 41, "metadata": {}, "output_type": "execute_result" } ], "source": [ "val m5 = m2 + (6 -> \"Michael\") // 6 used to be bound to the string \"Martin\"" ] }, { "cell_type": "code", "execution_count": 42, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "\u001b[36mm6\u001b[39m: \u001b[32mMap\u001b[39m[\u001b[32mInt\u001b[39m, \u001b[32mString\u001b[39m] = \u001b[33mHashMap\u001b[39m(\n", " \u001b[32m5\u001b[39m -> \u001b[32m\"Odersky\"\u001b[39m,\n", " \u001b[32m1\u001b[39m -> \u001b[32m\"Hello\"\u001b[39m,\n", " \u001b[32m6\u001b[39m -> \u001b[32m\"Martin\"\u001b[39m,\n", " \u001b[32m9\u001b[39m -> \u001b[32m\"Object Oriented\"\u001b[39m,\n", " \u001b[32m2\u001b[39m -> \u001b[32m\"World\"\u001b[39m,\n", " \u001b[32m7\u001b[39m -> \u001b[32m\"Functional\"\u001b[39m,\n", " \u001b[32m3\u001b[39m -> \u001b[32m\"Scala\"\u001b[39m,\n", " \u001b[32m8\u001b[39m -> \u001b[32m\"Programming\"\u001b[39m\n", ")\n", "\u001b[36mb6\u001b[39m: \u001b[32mBoolean\u001b[39m = \u001b[32mfalse\u001b[39m" ] }, "execution_count": 42, "metadata": {}, "output_type": "execute_result" } ], "source": [ "val m6 = m2 - 4 // Remove the binding for 4 (used to be bound to Java)\n", "val b6 = m6.contains(4)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Sets\n", "\n", "Scala has support for other data structures. A useful one is the Set data structure that supports single copy of each element. It also has fast membership testing, union, intersection and iteration operations. \n", "\n", "Documentation: https://www.scala-lang.org/api/2.13.16/scala/collection/Set.html\n", "\n", "Tutorials: https://www.tutorialspoint.com/scala/scala_sets.htm" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 5. Organzing Control Flow\n", "\n", "We will now look at how to define function calls, and control structures: if then else, and loops (we will cover them, but use them very rarely ;-) " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Defining functions in Scala\n", "\n", "Functions, as we mentioned earlier are _first class_ entities in Scala. Just like we can define integers, booleans, lists, sets, maps and pass them arround as data, in the same way, we can define functions and pass them around. In a way functions are also classes in Scala just like integers, booleans, lists and so on (but that is a subject for later, perhaps)." ] }, { "cell_type": "code", "execution_count": 43, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "defined \u001b[32mfunction\u001b[39m \u001b[36mmyFirstFunction\u001b[39m\n", "defined \u001b[32mfunction\u001b[39m \u001b[36mmyFirstFunctionAlternative\u001b[39m" ] }, "execution_count": 43, "metadata": {}, "output_type": "execute_result" } ], "source": [ "/* Define a function that given integer x, returns integer (x+2)^2 */\n", "def myFirstFunction(x : Int): Int = { // x : Int says that the type of x is Int. \n", " // The \":Int\" before = sign says that the function's return value is an integer\n", " val y = x + 2 // Define y\n", " val z = y * y // Define z\n", " return z // Return z\n", "}\n", "\n", "/* Here is a different way of writing the same function, take careful note */\n", "def myFirstFunctionAlternative(x: Int): Int = {\n", " // In Scala statements must be separated by ; but ; is optional because in many cases, the compiler does semicolon inference\n", " val y = x + 2 ; // ; is optional here, because compiler will infer it\n", " val z = y * y ; // ; is optional here, because compiler will infer it\n", " z //return is optional because if we have a composition of statements S1; S2; S3; ... ; Sn, it evaluates to whatever (Sn) evaluates to.\n", "}\n", "\n" ] }, { "cell_type": "code", "execution_count": 44, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "defined \u001b[32mfunction\u001b[39m \u001b[36mrepeatString\u001b[39m\n", "defined \u001b[32mfunction\u001b[39m \u001b[36mrepeatStringAlternative\u001b[39m" ] }, "execution_count": 44, "metadata": {}, "output_type": "execute_result" } ], "source": [ "/* Write a function that given a string x and integer y, returns the string that is x repeated y times. */\n", "\n", "def repeatString(x: String, k: Int): String = { // We play nice and annotate types to tell scala to expect \n", " // x as a string and k as an integer and return a string\n", " // Make a mutable accumulator called retValue\n", " var retValue = \"\"\n", " for (i <- 1 to k ){ \n", " retValue = retValue + x // Keep adding x to it k times\n", " }\n", " return retValue // return\n", "}\n", "\n", "/* Write a function that given a string x and integer k, repeats x, k times. */\n", "def repeatStringAlternative(x: String, k: Int): String = {\n", " // As a PL student, you should be able to write the same functionally.\n", " // Can't do so yet? Do not despair, we will look at Sequences, map, reduce and fold coming right up.\n", " (1 to k).foldLeft(\"\") ( (v, _) => v + x ) // Do not Panic, we will demystify this in due time :-)\n", "}" ] }, { "cell_type": "code", "execution_count": 45, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "\u001b[36ms\u001b[39m: \u001b[32mString\u001b[39m = \u001b[32m\"Donkey\"\u001b[39m\n", "\u001b[36mt\u001b[39m: \u001b[32mString\u001b[39m = \u001b[32m\"DonkeyDonkeyDonkeyDonkeyDonkey\"\u001b[39m\n", "\u001b[36mu\u001b[39m: \u001b[32mString\u001b[39m = \u001b[32m\"DonkeyDonkeyDonkeyDonkeyDonkey\"\u001b[39m" ] }, "execution_count": 45, "metadata": {}, "output_type": "execute_result" } ], "source": [ "val s =\"Donkey\"\n", "val t = repeatString(s, 5)\n", "val u = repeatStringAlternative(s, 5)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Scala supports recursive functions quite naturally." ] }, { "cell_type": "code", "execution_count": 46, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "defined \u001b[32mfunction\u001b[39m \u001b[36mfactorial\u001b[39m" ] }, "execution_count": 46, "metadata": {}, "output_type": "execute_result" } ], "source": [ "def factorial (n : Int): Int = {\n", " if (n <= 0) { \n", " 1 \n", " } \n", " else {\n", " n * factorial(n -1)\n", " }\n", " \n", "}" ] }, { "cell_type": "code", "execution_count": 47, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "\u001b[36ms\u001b[39m: \u001b[32mInt\u001b[39m = \u001b[32m120\u001b[39m\n", "\u001b[36mt\u001b[39m: \u001b[32mInt\u001b[39m = \u001b[32m2004310016\u001b[39m\n", "\u001b[36mw\u001b[39m: \u001b[32mInt\u001b[39m = \u001b[32m0\u001b[39m" ] }, "execution_count": 47, "metadata": {}, "output_type": "execute_result" } ], "source": [ "val s = factorial(5)\n", "val t = factorial(15)\n", "val w = factorial(45) // why is 45! = 0?\n" ] }, { "cell_type": "code", "execution_count": 48, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "defined \u001b[32mfunction\u001b[39m \u001b[36mbigFactorial\u001b[39m" ] }, "execution_count": 48, "metadata": {}, "output_type": "execute_result" } ], "source": [ "// Scala supports a type called BigInt for arbitrary length numbers and BigDecimal for arbitrary precision arithmetic.\n", "def bigFactorial(n: scala.math.BigInt): scala.math.BigInt = { // You can just write BigInt since scala.math is auto imported here\n", " if (n <= 0) { \n", " BigInt(1)\n", " } else {\n", " n * bigFactorial(n - 1)\n", " }\n", "}\n" ] }, { "cell_type": "code", "execution_count": 48, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "\u001b[36ms1\u001b[39m: \u001b[32mBigInt\u001b[39m = 120\n", "\u001b[36ms2\u001b[39m: \u001b[32mBigInt\u001b[39m = 1307674368000\n", "\u001b[36ms3\u001b[39m: \u001b[32mBigInt\u001b[39m = 119622220865480194561963161495657715064383733760000000000" ] }, "execution_count": 48, "metadata": {}, "output_type": "execute_result" } ], "source": [ "val s1 = bigFactorial(5)\n", "val s2 = bigFactorial(15)\n", "val s3 = bigFactorial(45)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "When you write `val x = 10` in Scala, the interpreter/compiler figures out that `x` is of type `Int`. What about a function definition? What is its type?\n" ] }, { "cell_type": "code", "execution_count": 49, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "defined \u001b[32mfunction\u001b[39m \u001b[36mmySecondFunction\u001b[39m" ] }, "execution_count": 49, "metadata": {}, "output_type": "execute_result" } ], "source": [ "def mySecondFunction(x : Int): Int = { \n", " x + 10\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We denote the type of `mySecondFunction` as `Int => Int`. This stands for a function that takes in an `Int` argument and returns an `Int` argument.\n", "\n", "### Functions as arguments to other functions.\n", "\n", "Scala can take in a function whose argument is another function. These are widely used for instance to implement features in programs such as _callbacks_, _continuations_ and so on.\n", "\n", "Let us look at some examples that demonstrate how we can do this.\n" ] }, { "cell_type": "code", "execution_count": 50, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "defined \u001b[32mfunction\u001b[39m \u001b[36mapplyFunction\u001b[39m" ] }, "execution_count": 50, "metadata": {}, "output_type": "execute_result" } ], "source": [ "def applyFunction(f: Int => Int, x : Int):Int = { f(x)}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "`applyFunction` takes as the first argument a function `f` from integer to integer and the second argument `x` an integer. It proceeds to apply the function passed in to the integer and returns the result, an integer" ] }, { "cell_type": "code", "execution_count": 51, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "\u001b[36mx\u001b[39m: \u001b[32mInt\u001b[39m = \u001b[32m3628800\u001b[39m\n", "\u001b[36my\u001b[39m: \u001b[32mInt\u001b[39m = \u001b[32m2209\u001b[39m\n", "\u001b[36mz\u001b[39m: \u001b[32mInt\u001b[39m = \u001b[32m24\u001b[39m" ] }, "execution_count": 51, "metadata": {}, "output_type": "execute_result" } ], "source": [ "val x = applyFunction(factorial, 10) // Same as factorial(10)\n", "val y = applyFunction(myFirstFunction, 45) // same as myFirstFunction(45)\n", "val z = applyFunction(mySecondFunction, 14) // same as mySecondFunction(14)" ] }, { "cell_type": "code", "execution_count": 51, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "cmd52.sc:2: type mismatch;\n", " found : (String, Int) => String\n", " required: Int => Int\n", "val z = applyFunction(repeatString, 15) // You get a type mismatch message.\n", " ^\n", "Compilation Failed" ] } ], "source": [ "// Wrong application\n", "val z = applyFunction(repeatString, 15) // You get a type mismatch message.\n", "// The message below tells you exactly what went wrong.\n", "// repeatString has type (String, Int) => String, whereas we were expected to \n", "// provide a function Int => Int." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Code Blocks\n", "\n", "You can write code blocks in Scala enclosed inside curly braces `{` and `}`. A code block is a sequence of statements and the value of the block is that of the very last statement in the block.\n" ] }, { "cell_type": "code", "execution_count": 52, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "I can print all types of nonsense\n", "Even though my final aim is to produce\n", "Nothing more than zero\n", "HelloGagaUnreadableCode" ] }, { "data": { "text/plain": [ "\u001b[36mblk1\u001b[39m: \u001b[32mInt\u001b[39m = \u001b[32m0\u001b[39m\n", "\u001b[36mblk2\u001b[39m: \u001b[32mString\u001b[39m = \u001b[32m\"Hello World Expression\"\u001b[39m\n", "\u001b[36mlst3\u001b[39m: \u001b[32mList\u001b[39m[\u001b[32mInt\u001b[39m] = \u001b[33mList\u001b[39m(\u001b[32m1\u001b[39m, \u001b[32m2\u001b[39m, \u001b[32m3\u001b[39m, \u001b[32m4\u001b[39m)" ] }, "execution_count": 52, "metadata": {}, "output_type": "execute_result" } ], "source": [ "\n", "// This is an example of using a {} to define a expression. Note that the first three lines are executed and the final \n", "// value of the entire expression is that of the very last statement. In this case that value is 0\n", "val blk1 = { println(\"I can print all types of nonsense\")\n", " println(\"Even though my final aim is to produce\")\n", " println(\"Nothing more than zero\")\n", " 0\n", "}\n", "\n", "// We can write temporary variables inside these {} \n", "// Note that ; is technically the statement separator in Scala\n", "// However, scala has ; inference and thus, we use it only\n", "// when the compiler is too confused, to insert it on its own\n", "val blk2 = {\n", " val tmp1 = \"Hello\";\n", " val tmp2 = \"World\" ; val tmp3 = \"Expression\";\n", " tmp1 + \" \" + tmp2 + \" \" + tmp3 \n", "}\n", "\n", "// Here is another example that creates a list List(1,2,3,4)\n", "val lst3 = List({print(\"Hello\"); 1}, {print(\"Gaga\"); 2}, \n", " {print(\"Unreadable\"); 3}, {print(\"Code\"); 4})\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Control Structures: If-Then-Else and Loops\n", "\n", "Scala defines if then else constructs just like any other language. You can also have if then else expressions. In C these are similar to the conditional expression `(condition)? (option1): option2`" ] }, { "cell_type": "code", "execution_count": 53, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "\u001b[36mv\u001b[39m: \u001b[32mInt\u001b[39m = \u001b[32m10\u001b[39m\n", "\u001b[36mw\u001b[39m: \u001b[32mInt\u001b[39m = \u001b[32m45\u001b[39m" ] }, "execution_count": 53, "metadata": {}, "output_type": "execute_result" } ], "source": [ "val v = if (s1 >= 120) 10 else 15 // If then else expressions\n", "val w = if (s2 >= 1000 && s1 >= 250) {50} else {45} // The curly braces are optional" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If then else can be used inside functions for control flow." ] }, { "cell_type": "code", "execution_count": 54, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "defined \u001b[32mfunction\u001b[39m \u001b[36miffyFunction\u001b[39m" ] }, "execution_count": 54, "metadata": {}, "output_type": "execute_result" } ], "source": [ "def iffyFunction(v: Int): String = {\n", " if (v >= 100){\n", " return \"Too Large\" // The return statement is optional \n", " } else {\n", " if (v <= 0)\n", " { return \"Negative\"}\n", " else \n", " { return \"Positive\"}\n", " }\n", "}" ] }, { "cell_type": "code", "execution_count": 55, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "(Positive,Too Large,Negative)" ] } ], "source": [ "print(iffyFunction(20), iffyFunction(120), iffyFunction(-30))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Converting Loops To Recursion.\n", "\n", "Loops often require you to define mutable variables. Here is a while loop inside a function to check if an input list contains an odd number. It is how we commonly do it in imperative languages." ] }, { "cell_type": "code", "execution_count": 56, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "defined \u001b[32mfunction\u001b[39m \u001b[36mhasOddNumber\u001b[39m" ] }, "execution_count": 56, "metadata": {}, "output_type": "execute_result" } ], "source": [ "def hasOddNumber(lst: List[Int]): Boolean = {\n", " val n = lst.length\n", " var j = 0\n", " while (j < n) {\n", " if (lst(j) %2 == 1){\n", " return true\n", " }\n", " j += 1\n", " }\n", " return false\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The very same function can be implemented recursively avoiding the while loop and in the process, using just immutable values." ] }, { "cell_type": "code", "execution_count": 57, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "defined \u001b[32mfunction\u001b[39m \u001b[36mhasOddNumberRecHelper\u001b[39m\n", "defined \u001b[32mfunction\u001b[39m \u001b[36mhasOddNumberRec\u001b[39m" ] }, "execution_count": 57, "metadata": {}, "output_type": "execute_result" } ], "source": [ "def hasOddNumberRecHelper(lst: List[Int], j: Int): Boolean = {\n", " if (j >= lst.length) { false}\n", " else if (lst(j) %2 == 1) { true }\n", " else {\n", " hasOddNumberRecHelper(lst, j+1) // Recursive call\n", " }\n", "}\n", "\n", "def hasOddNumberRec(lst: List[Int]): Boolean = hasOddNumberRecHelper(lst, 0)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Normally, we consider recursion to be inefficient since it involves\n", "a growth in the stack depth of the computation. However, there is an exception to this called _tail recursion_. Here the value of the recursive call is just returned to the caller as is, without further computation on it. Note that `hasOddNumberRecHelper` is tail recursive, but `factorial` is not.\n", "\n", "More about this coming up. In the meantime it is excellent practice to convert from while loops to recursive versions." ] }, { "cell_type": "code", "execution_count": 53, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "defined \u001b[32mfunction\u001b[39m \u001b[36mcountZs\u001b[39m\n", "defined \u001b[32mfunction\u001b[39m \u001b[36mcountZsRec\u001b[39m" ] }, "execution_count": 53, "metadata": {}, "output_type": "execute_result" } ], "source": [ "def countZs(s: String): Int = {\n", " // How many occurrences of Z in a string.\n", " var count = 0\n", " for(i <- 0 until s.length){\n", " if (s(i) == 'Z' || s(i) == 'z') { count = count + 1}\n", " }\n", " return count\n", "}\n", "\n", "def countZsRec(s: String, i: Int): Int = {\n", " if (i >= s.length){ return 0}\n", " else {\n", " if (s(i) == 'Z' || s(i) == 'z') {\n", " return 1 + countZsRec(s, i+1) // This is not tail recursive: we will fix it later.\n", " } else {\n", " return countZsRec(s, i+1)\n", " }\n", " }\n", "}" ] }, { "cell_type": "code", "execution_count": 54, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "\u001b[36mi\u001b[39m: \u001b[32mInt\u001b[39m = \u001b[32m4\u001b[39m\n", "\u001b[36mj\u001b[39m: \u001b[32mInt\u001b[39m = \u001b[32m6\u001b[39m" ] }, "execution_count": 54, "metadata": {}, "output_type": "execute_result" } ], "source": [ "val i = countZs(\"ZZHellZZo\")\n", "val j = countZsRec(\"ZZZZHellZZo\", 0)" ] } ], "metadata": { "kernelspec": { "display_name": "Scala", "language": "scala", "name": "scala" }, "language_info": { "codemirror_mode": "text/x-scala", "file_extension": ".sc", "mimetype": "text/x-scala", "name": "scala", "nbconvert_exporter": "script", "version": "2.13.14" } }, "nbformat": 4, "nbformat_minor": 4 }