├── .gitignore ├── .idea ├── .gitignore ├── .name ├── codeStyles │ ├── Project.xml │ └── codeStyleConfig.xml ├── misc.xml ├── modules.xml ├── modules │ ├── scala-3-beginners-build.iml │ └── scala-3-beginners.iml ├── sbt.xml ├── scala_compiler.xml ├── scala_settings.xml └── vcs.xml ├── README.md ├── build.sbt ├── project └── build.properties └── src └── main └── scala └── com └── rockthejvm ├── part1basics ├── CBNvsCBV.scala ├── DefaultArgs.scala ├── Expressions.scala ├── Functions.scala ├── Recursion.scala ├── StringOps.scala └── ValuesAndTypes.scala ├── part2oop ├── AbstractDataTypes.scala ├── AccessModifiers.scala ├── AnonymousClasses.scala ├── CaseClasses.scala ├── Enums.scala ├── Exceptions.scala ├── Generics.scala ├── Inheritance.scala ├── MethodNotations.scala ├── OOBasics.scala ├── Objects.scala ├── PackagesImports.scala └── PreventingInheritance.scala ├── part3fp ├── AnonymousFunctions.scala ├── HOFsCurrying.scala ├── HandlingFailure.scala ├── LinearCollections.scala ├── MapFlatMapFilterFor.scala ├── Options.scala ├── TuplesMaps.scala └── WhatsAFunction.scala ├── part4power ├── AllThePatterns.scala ├── BracelessSyntax.scala ├── ImperativeProgramming.scala ├── PatternMatching.scala └── PatternsEverywhere.scala ├── playground └── Playground.scala └── practice ├── LList.scala ├── Maybe.scala └── TuplesMapsExercises.scala /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.toptal.com/developers/gitignore/api/scala,sbt,intellij,java 3 | # Edit at https://www.toptal.com/developers/gitignore?templates=scala,sbt,intellij,java 4 | 5 | ### Intellij ### 6 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider 7 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 8 | 9 | # User-specific stuff 10 | .idea/**/workspace.xml 11 | .idea/**/tasks.xml 12 | .idea/**/usage.statistics.xml 13 | .idea/**/dictionaries 14 | .idea/**/shelf 15 | 16 | # Generated files 17 | .idea/**/contentModel.xml 18 | 19 | # Sensitive or high-churn files 20 | .idea/**/dataSources/ 21 | .idea/**/dataSources.ids 22 | .idea/**/dataSources.local.xml 23 | .idea/**/sqlDataSources.xml 24 | .idea/**/dynamic.xml 25 | .idea/**/uiDesigner.xml 26 | .idea/**/dbnavigator.xml 27 | 28 | # Gradle 29 | .idea/**/gradle.xml 30 | .idea/**/libraries 31 | 32 | # Gradle and Maven with auto-import 33 | # When using Gradle or Maven with auto-import, you should exclude module files, 34 | # since they will be recreated, and may cause churn. Uncomment if using 35 | # auto-import. 36 | # .idea/artifacts 37 | # .idea/compiler.xml 38 | # .idea/jarRepositories.xml 39 | # .idea/modules.xml 40 | # .idea/*.iml 41 | # .idea/modules 42 | # *.iml 43 | # *.ipr 44 | 45 | # CMake 46 | cmake-build-*/ 47 | 48 | # Mongo Explorer plugin 49 | .idea/**/mongoSettings.xml 50 | 51 | # File-based project format 52 | *.iws 53 | 54 | # IntelliJ 55 | out/ 56 | 57 | # mpeltonen/sbt-idea plugin 58 | .idea_modules/ 59 | 60 | # JIRA plugin 61 | atlassian-ide-plugin.xml 62 | 63 | # Cursive Clojure plugin 64 | .idea/replstate.xml 65 | 66 | # Crashlytics plugin (for Android Studio and IntelliJ) 67 | com_crashlytics_export_strings.xml 68 | crashlytics.properties 69 | crashlytics-build.properties 70 | fabric.properties 71 | 72 | # Editor-based Rest Client 73 | .idea/httpRequests 74 | 75 | # Android studio 3.1+ serialized cache file 76 | .idea/caches/build_file_checksums.ser 77 | 78 | ### Intellij Patch ### 79 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 80 | 81 | # *.iml 82 | # modules.xml 83 | # .idea/misc.xml 84 | # *.ipr 85 | 86 | # Sonarlint plugin 87 | # https://plugins.jetbrains.com/plugin/7973-sonarlint 88 | .idea/**/sonarlint/ 89 | 90 | # SonarQube Plugin 91 | # https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin 92 | .idea/**/sonarIssues.xml 93 | 94 | # Markdown Navigator plugin 95 | # https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced 96 | .idea/**/markdown-navigator.xml 97 | .idea/**/markdown-navigator-enh.xml 98 | .idea/**/markdown-navigator/ 99 | 100 | # Cache file creation bug 101 | # See https://youtrack.jetbrains.com/issue/JBR-2257 102 | .idea/$CACHE_FILE$ 103 | 104 | # CodeStream plugin 105 | # https://plugins.jetbrains.com/plugin/12206-codestream 106 | .idea/codestream.xml 107 | 108 | ### Java ### 109 | # Compiled class file 110 | *.class 111 | 112 | # Log file 113 | *.log 114 | 115 | # BlueJ files 116 | *.ctxt 117 | 118 | # Mobile Tools for Java (J2ME) 119 | .mtj.tmp/ 120 | 121 | # Package Files # 122 | *.jar 123 | *.war 124 | *.nar 125 | *.ear 126 | *.zip 127 | *.tar.gz 128 | *.rar 129 | 130 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 131 | hs_err_pid* 132 | 133 | ### SBT ### 134 | # Simple Build Tool 135 | # http://www.scala-sbt.org/release/docs/Getting-Started/Directories.html#configuring-version-control 136 | 137 | dist/* 138 | target/ 139 | lib_managed/ 140 | src_managed/ 141 | project/boot/ 142 | project/plugins/project/ 143 | .history 144 | .cache 145 | .lib/ 146 | .bsp 147 | 148 | ### Scala ### 149 | 150 | # End of https://www.toptal.com/developers/gitignore/api/scala,sbt,intellij,java 151 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | -------------------------------------------------------------------------------- /.idea/.name: -------------------------------------------------------------------------------- 1 | scala-3-beginners -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 10 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/modules/scala-3-beginners-build.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 117 | -------------------------------------------------------------------------------- /.idea/modules/scala-3-beginners.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /.idea/sbt.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 19 | -------------------------------------------------------------------------------- /.idea/scala_compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/scala_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | ## The official repository for the Scala 3 & FP Essentials course 3 | 4 | Powered by [Rock the JVM!](rockthejvm.com) 5 | 6 | This repository contains the code we wrote during [Rock the JVM's Scala 3 & FP Essentials course](https://rockthejvm.com/course/scala). Unless explicitly mentioned, the code in this repository is exactly what was caught on camera. 7 | 8 | ### Installation 9 | 10 | How to install: 11 | - either clone the repo or download as zip 12 | - open with IntelliJ as it's a simple IDEA project 13 | 14 | ### Getting Started 15 | 16 | Run this command in a git terminal to reset the code in its starting/clean state: 17 | 18 | ``` 19 | git checkout start 20 | ``` 21 | 22 | This repo also has Git tags for intermediate states of the code while we were working in the course. You can check out the appropriate tags for the different stages of the course. Useful especially for longer exercises where we modify the same code over multiple videos. 23 | 24 | The tags are: 25 | 26 | * `start` 27 | * `1.1-values-types` 28 | * `1.2-expressions` 29 | * `1.3-functions` 30 | * `1.5-recursion` 31 | * `1.6-cbn-cbv` 32 | * `1.7-default-args` 33 | * `1.8-string-ops` 34 | * `2.1-oo-basics` 35 | * `2.2-oo-basics-exercises` 36 | * `2.3-method-notations` 37 | * `2.4-inheritance` 38 | * `2.5-access-modifiers` 39 | * `2.6-preventing-inheritance` 40 | * `2.7-objects` 41 | * `2.8-abstract-classes-traits` 42 | * `2.9-list-part-1` 43 | * `2.10-generics` 44 | * `2.11-anonymous-classes` 45 | * `2.12-list-part-2` 46 | * `2.13-case-classes` 47 | * `2.14-enums` 48 | * `2.15-exceptions` 49 | * `2.16-imports-exports` 50 | * `3.1-what-a-function-is` 51 | * `3.2-anonymous-functions` 52 | * `3.3-hofs-currying` 53 | * `3.4-hofs-currying-exercises` 54 | * `3.6-map-flatmap-filter-for` 55 | * `3.7-linear-collections` 56 | * `3.8-tuples-maps` 57 | * `3.9-tuples-maps-exercises` 58 | * `3.10-option` 59 | * `3.11-try` 60 | * `4.1-pattern-matching` 61 | * `4.2-all-the-patterns` 62 | * `4.3-pattern-matching-everywhere` 63 | * `4.4-braceless-syntax` 64 | 65 | 66 | ### Contributions 67 | 68 | If you have changes to suggest to this repo, either 69 | - submit a GitHub issue 70 | - tell me in the course Q/A forum 71 | - submit a pull request! 72 | -------------------------------------------------------------------------------- /build.sbt: -------------------------------------------------------------------------------- 1 | val scala3Version = "3.3.5" 2 | 3 | lazy val root = project 4 | .in(file(".")) 5 | .settings( 6 | name := "scala-3-beginners", 7 | version := "0.1.0", 8 | 9 | scalaVersion := scala3Version, 10 | 11 | libraryDependencies += "com.novocode" % "junit-interface" % "0.11" % "test" 12 | ) 13 | -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.10.10 2 | -------------------------------------------------------------------------------- /src/main/scala/com/rockthejvm/part1basics/CBNvsCBV.scala: -------------------------------------------------------------------------------- 1 | package com.rockthejvm.part1basics 2 | 3 | object CBNvsCBV { 4 | 5 | // CBV = call by value = arguments are evaluated before function invocation 6 | def aFunction(arg: Int): Int = arg + 1 7 | val aComputation = aFunction(23 + 67) 8 | 9 | // CBN = call by name = arguments are passed LITERALLY, evaluated at every reference 10 | def aByNameFunction(arg: => Int): Int = arg + 1 11 | val anotherComputation = aByNameFunction(23 + 67) 12 | 13 | def printTwiceByValue(x: Long): Unit = { 14 | println("By value: " + x) 15 | println("By value: " + x) 16 | } 17 | 18 | /* 19 | CBN major features: 20 | - delayed evaluation of the argument 21 | - argument is evaluated every time it is used 22 | */ 23 | def printTwiceByName(x: => Long): Unit = { 24 | println("By name: " + x) 25 | println("By name: " + x) 26 | } 27 | 28 | /* 29 | Another benefit of CBN is that it delays the evaluation of the argument until it's used. 30 | If it's not used, it's not evaluated. 31 | */ 32 | def infinite(): Int = 1 + infinite() 33 | def printFirst(x: Int, y: => Int) = println(x) 34 | 35 | 36 | def main(args: Array[String]): Unit = { 37 | printTwiceByValue(System.nanoTime()) // prints the same instant twice 38 | printTwiceByName(System.nanoTime()) // prints two different instants 39 | 40 | printFirst(42, infinite()) // works 41 | // printFirst(infinite(), 42) // crashes 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/scala/com/rockthejvm/part1basics/DefaultArgs.scala: -------------------------------------------------------------------------------- 1 | package com.rockthejvm.part1basics 2 | 3 | import scala.annotation.tailrec 4 | 5 | object DefaultArgs { 6 | 7 | @tailrec 8 | def sumUntilTailrec(x: Int, accumulator: Int = 0): Int = 9 | if (x <= 0) accumulator 10 | else sumUntilTailrec(x - 1, accumulator + x) 11 | 12 | val sumUntil100 = sumUntilTailrec(100) // additional arg passed automatically 13 | 14 | // when you use a function most of the time with the same value = default arguments 15 | def savePicture(dirPath: String, name: String, format: String = "jpg", width: Int = 1920, height: Int = 1080): Unit = 16 | println("Saving picture in format " + format + " in path " + dirPath) 17 | 18 | 19 | def main(args: Array[String]): Unit = { 20 | // default args are injected 21 | savePicture("/users/daniel/photos", "myphoto") 22 | // pass explicit different values for default args 23 | savePicture("/users/daniel/photos", "myphoto", "png") 24 | // pass values after the default argument 25 | savePicture("/users/daniel/photos", "myphoto", width = 800, height = 600) 26 | // naming arguments allow passing in a different order 27 | savePicture("/users/daniel/photos", "myphoto", height = 600, width = 800) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/scala/com/rockthejvm/part1basics/Expressions.scala: -------------------------------------------------------------------------------- 1 | package com.rockthejvm.part1basics 2 | 3 | object Expressions { 4 | 5 | // expressions are structures that can be evaluated to a value 6 | val meaningOfLife = 40 + 2 7 | 8 | // mathematical expressions: +, -, *, /, bitwise |, &, <<, >>, >>> 9 | val mathExpression = 2 + 3 * 4 10 | 11 | // comparison expressions: <, <=, >, >=, ==, != 12 | val equalityTest = 1 == 2 13 | 14 | // boolean expressions: !, ||, && 15 | val nonEqualityTest = !equalityTest 16 | 17 | // instructions vs expressions 18 | // expressions are evaluated, instructions are executed 19 | // we think in terms of expressions 20 | 21 | // ifs are expressions 22 | val aCondition = true 23 | val anIfExpression = if (aCondition) 45 else 99 24 | 25 | // code blocks 26 | val aCodeBlock = { 27 | // local values 28 | val localValue = 78 29 | // expressions... 30 | 31 | // last expression = value of the block 32 | localValue + 54 33 | } 34 | 35 | // everything is an expression 36 | 37 | /** 38 | * Exercise: 39 | * Without running the code, what do you think these values will print out? 40 | */ 41 | // 1 42 | val someValue = { 43 | 2 < 3 44 | } 45 | 46 | // 2 47 | val someOtherValue = { 48 | if (someValue) 239 else 986 49 | 42 50 | } 51 | 52 | // 3 53 | val yetAnotherValue: Unit = println("Scala") 54 | val theUnit: Unit = () // Unit == "void" in other languages 55 | 56 | def main(args: Array[String]): Unit = { 57 | println(someValue) // true 58 | println(someOtherValue) // 42 59 | println(yetAnotherValue) // Scala, () 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/main/scala/com/rockthejvm/part1basics/Functions.scala: -------------------------------------------------------------------------------- 1 | package com.rockthejvm.part1basics 2 | 3 | object Functions { 4 | 5 | // function = reusable piece of code that you can invoke with some arguments and return a result 6 | def aFunction(a: String, b: Int): String = 7 | a + " " + b // ONE expression 8 | 9 | // function invocation 10 | val aFunctionInvocation = aFunction("Scala", 999999999) 11 | 12 | def aNoArgFunction(): Int = 45 13 | def aParamterlessFunction: Int = 45 14 | 15 | // functions can be recursive 16 | def stringConcatenation(str: String, n: Int): String = 17 | if (n == 0) "" 18 | else if (n == 1) str 19 | else str + stringConcatenation(str, n - 1) 20 | 21 | /* 22 | sc("Scala", 3) = "Scala" + sc("Scala", 2) = "Scala" + "ScalaScala" = "ScalaScalaScala" 23 | sc("Scala", 2) = "Scala" + sc("Scala", 1) = "Scala" + "Scala" = "ScalaScala" 24 | sc("Scala", 1) = "Scala" 25 | */ 26 | val scalax3 = stringConcatenation("Scala", 3) 27 | // when you need loops, use RECURSION. 28 | 29 | // "void" functions 30 | def aVoidFunction(aString: String): Unit = 31 | println(aString) 32 | 33 | def computeDoubleStringWithSideEffect(aString: String): String = { 34 | aVoidFunction(aString) // Unit 35 | aString + aString // meaningful value 36 | } // discouraging side effects 37 | 38 | def aBigFunction(n: Int): Int = { 39 | // small, auxiliary functions inside 40 | def aSmallerFunction(a: Int, b: Int): Int = a + b 41 | 42 | aSmallerFunction(n, n + 1) 43 | } 44 | 45 | /** 46 | * Exercises 47 | * 1. A greeting function (name, age) => "Hi my name is $name and I am $age years old." 48 | * 2. Factorial function n => 1 * 2 * 3 * .. * n 49 | * 3. Fibonacci function 50 | * fib(1) = 1 51 | * fib(2) = 1 52 | * fib(3) = 1 + 1 53 | * fib(n) = fib(n-1) + fib(n-2) 54 | * 55 | * 4. Tests if a number is prime 56 | */ 57 | 58 | // 1 59 | def greetingForKids(name: String, age: Int): String = 60 | "Hi, my name is " + name + " and I am " + age + " years old." 61 | 62 | // 2 63 | /* 64 | f(5) = 5 * f(4) = 120 65 | f(4) = 4 * f(3) = 24 66 | f(3) = 3 * f(2) = 6 67 | f(2) = 2 * f(1) = 2 68 | f(1) = 1 69 | */ 70 | def factorial(n: Int): Int = 71 | if (n <= 0) 0 72 | else if (n == 1) 1 73 | else n * factorial(n - 1) 74 | 75 | // 3 76 | /* 77 | fib(5) = fib(4) + fib(3) = 5 78 | fib(4) = fib(3) + fib(2) = 3 79 | fib(3) = fib(2) + fib(1) = 2 80 | fib(2) = 1 81 | fib(1) = 1 82 | */ 83 | def fibonacci(n: Int): Int = 84 | if (n <= 2) 1 85 | else fibonacci(n - 1) + fibonacci(n - 2) 86 | 87 | // 4 88 | /* 89 | isPrime(7) = isPrimeUntil(3) = true 90 | ipu(3) = 7 % 3 != 0 && ipu(2) = true 91 | ipu(2) = 7 % 2 != 0 && ipu(1) = true 92 | ipu(1) = true 93 | */ 94 | def isPrime(n: Int): Boolean = { 95 | def isPrimeUntil(t: Int): Boolean = 96 | if (t <= 1) true 97 | else n % t != 0 && isPrimeUntil(t - 1) 98 | 99 | isPrimeUntil(n / 2) 100 | } 101 | 102 | def main(args: Array[String]): Unit = { 103 | println(greetingForKids("Daniel", 9)) 104 | println(factorial(5)) 105 | println(fibonacci(5)) 106 | println(isPrime(7)) 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/main/scala/com/rockthejvm/part1basics/Recursion.scala: -------------------------------------------------------------------------------- 1 | package com.rockthejvm.part1basics 2 | 3 | import scala.annotation.tailrec 4 | 5 | object Recursion { 6 | 7 | // "repetition" = recursion 8 | def sumUntil(n: Int): Int = 9 | if (n <= 0) 0 10 | else n + sumUntil(n - 1) // "stack" recursion 11 | 12 | def sumUntil_v2(n: Int): Int = { 13 | /* 14 | sut(10, 0) = 15 | sut(9, 10) = 16 | sut(8, 9 + 10) = 17 | sut(7, 8 + 9 + 10) = 18 | ... 19 | sut(0, 1 + 2 + 3 + .. + 9 + 10) 20 | = 1 + 2 + 3 + .. + 10 21 | */ 22 | @tailrec 23 | def sumUntilTailrec(x: Int, accumulator: Int): Int = 24 | if (x <= 0) accumulator 25 | else sumUntilTailrec(x - 1, accumulator + x) // TAIL recursion = recursive call occurs LAST in its code path 26 | // no further stack frames necessary = no more risk of SO 27 | 28 | sumUntilTailrec(n, 0) 29 | } 30 | 31 | def sumNumbersBetween(a: Int, b: Int): Int = 32 | if (a > b) 0 33 | else a + sumNumbersBetween(a + 1, b) 34 | 35 | def sumNumbersBetween_v2(a: Int, b: Int): Int = { 36 | @tailrec 37 | def sumTailrec(currentNumber: Int, accumulator: Int): Int = 38 | if (currentNumber > b) accumulator 39 | else sumTailrec(currentNumber + 1, currentNumber + accumulator) 40 | 41 | sumTailrec(a, 0) 42 | } 43 | 44 | /** 45 | * Exercises 46 | * 1. Concatenate a string n times 47 | * 2. Fibonacci function, tail recursive 48 | * 3. Is isPrime function tail recursive or not? 49 | */ 50 | 51 | // 1 52 | def concatenate(string: String, n: Int): String = { 53 | @tailrec 54 | def concatTailrec(remainingTimes: Int, accumulator: String): String = 55 | if (remainingTimes <= 0) accumulator 56 | else concatTailrec(remainingTimes - 1, string + accumulator) 57 | 58 | concatTailrec(n, "") 59 | } 60 | 61 | // 2 62 | def fibonacci(n: Int): Int = { 63 | def fiboTailrec(i: Int, last: Int, previous: Int): Int = 64 | if (i >= n) last 65 | else fiboTailrec(i + 1, last + previous, last) 66 | 67 | if (n <= 2) 1 68 | else fiboTailrec(2, 1, 1) 69 | } 70 | 71 | // 3 - yes, rephrasing: 72 | def isPrime(n: Int): Boolean = { 73 | def isPrimeUntil(t: Int): Boolean = 74 | if (t <= 1) true 75 | else if (n % t == 0) false 76 | else isPrimeUntil(t - 1) 77 | 78 | isPrimeUntil(n / 2) 79 | } 80 | 81 | def main(args: Array[String]): Unit = { 82 | println(sumUntil_v2(20000)) 83 | println(concatenate("Scala", 5)) 84 | println(fibonacci(7)) 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/main/scala/com/rockthejvm/part1basics/StringOps.scala: -------------------------------------------------------------------------------- 1 | package com.rockthejvm.part1basics 2 | 3 | object StringOps { 4 | 5 | val aString: String = "Hello, I am learning Scala" 6 | 7 | // string functions 8 | val secondChar = aString.charAt(1) 9 | val firstWord = aString.substring(0, 5) // "Hello" 10 | val words = aString.split(" ") // Array("Hello,", "I", "am", "learning", "Scala") 11 | val startsWithHello = aString.startsWith("Hello") // true 12 | val allDashes = aString.replace(' ', '-') 13 | val allUppercase = aString.toUpperCase() // also toLowerCase() 14 | val nChars = aString.length 15 | 16 | // other functions 17 | val reversed = aString.reverse 18 | val aBunchOfChars = aString.take(10) 19 | 20 | // parse to numeric 21 | val numberAsString = "2" 22 | val number = numberAsString.toInt 23 | 24 | // s-interpolation 25 | val name = "Alice" 26 | val age = 12 27 | val greeting = "Hello, I'm " + name + " and I am " + age + "years old." 28 | val greeting_v2 = s"Hello, I'm $name and I'm $age years old." 29 | val greeting_v3 = s"Hello, I'm $name and I will be turning ${age + 1} years old." 30 | 31 | // f-interpolation 32 | val speed = 1.2f 33 | val myth = f"$name can eat $speed%2.5f burgers per minute." 34 | 35 | // raw-interpolation 36 | val escapes = raw"This is a \n newline" 37 | 38 | def main(args: Array[String]): Unit = { 39 | println(escapes) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/scala/com/rockthejvm/part1basics/ValuesAndTypes.scala: -------------------------------------------------------------------------------- 1 | package com.rockthejvm.part1basics 2 | 3 | object ValuesAndTypes { 4 | 5 | // values 6 | val meaningOfLife: Int = 42 // const int meaningOfLife = 42 7 | 8 | // reassigning is not allowed 9 | // meaningOfLife = 45 10 | 11 | // type inferrence 12 | val anInteger = 67 // : Int is optional 13 | 14 | // common types 15 | val aBoolean: Boolean = false 16 | val aChar: Char = 'a' 17 | val anInt: Int = 78 // 4 bytes 18 | val aShort: Short = 5263 // 2 bytes 19 | val aLong: Long = 52789572389234L // 8 bytes 20 | val aFloat: Float = 2.4f // 4 bytes 21 | val aDouble: Double = 3.14 // 8 bytes 22 | 23 | // string 24 | val aString: String = "Scala" 25 | 26 | def main(args: Array[String]): Unit = { 27 | 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/scala/com/rockthejvm/part2oop/AbstractDataTypes.scala: -------------------------------------------------------------------------------- 1 | package com.rockthejvm.part2oop 2 | 3 | object AbstractDataTypes { 4 | 5 | abstract class Animal { 6 | val creatureType: String // abstract 7 | def eat(): Unit 8 | // non-abstract fields/methods allowed 9 | def preferredMeal: String = "anything" // "accessor methods" 10 | } 11 | 12 | // abstract classes can't be instantiated 13 | // val anAnimal: Animal = new Animal 14 | 15 | // non-abstract classes must implement the abstract fields/methods 16 | class Dog extends Animal { 17 | override val creatureType = "domestic" 18 | override def eat(): Unit = println("crunching this bone") 19 | // overriding is legal for everything 20 | override val preferredMeal: String = "bones" // overriding accessor method with a field 21 | } 22 | 23 | val aDog: Animal = new Dog 24 | 25 | // traits 26 | trait Carnivore { // Scala 3 - traits can have constructor args 27 | def eat(animal: Animal): Unit 28 | } 29 | 30 | class TRex extends Carnivore { 31 | override def eat(animal: Animal): Unit = println("I'm a T-Rex, I eat animals") 32 | } 33 | 34 | // practical difference abstract classes vs traits 35 | // one class inheritance 36 | // multiple traits inheritance 37 | trait ColdBlooded 38 | class Crocodile extends Animal with Carnivore with ColdBlooded { 39 | override val creatureType = "croc" 40 | override def eat(): Unit = println("I'm a croc, I just crunch stuff") 41 | override def eat(animal: Animal): Unit = println("croc eating animal") 42 | } 43 | 44 | /* 45 | philosophical difference abstract classes vs traits 46 | - abstract classes are THINGS 47 | - traits are BEHAVIORS 48 | */ 49 | 50 | /* 51 | Any 52 | AnyRef 53 | All classes we write 54 | scala.Null (the null reference) 55 | AnyVal 56 | Int, Boolean, Char, ... 57 | 58 | 59 | scala.Nothing 60 | */ 61 | 62 | val aNonExistentAnimal: Animal = null 63 | val anInt: Int = throw new NullPointerException 64 | 65 | def main(args: Array[String]): Unit = { 66 | 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/main/scala/com/rockthejvm/part2oop/AccessModifiers.scala: -------------------------------------------------------------------------------- 1 | package com.rockthejvm.part2oop 2 | 3 | object AccessModifiers { 4 | 5 | class Person(val name: String) { 6 | // protected == access to inside the class + children classes 7 | protected def sayHi(): String = s"Hi, my name is $name." 8 | // private = only accessible inside the class 9 | private def watchNetflix(): String = "I'm binge watching my favorite series..." 10 | } 11 | 12 | class Kid(override val name: String, age: Int) extends Person(name) { 13 | def greetPolitely(): String = // no modifier = "public" 14 | sayHi() + "I love to play!" 15 | } 16 | 17 | val aPerson = new Person("Alice") 18 | val aKid = new Kid("David", 5) 19 | 20 | // complication 21 | class KidWithParents(override val name: String, age: Int, momName: String, dadName: String) extends Person(name) { 22 | val mom = new Person(momName) 23 | val dad = new Person(dadName) 24 | 25 | // can't call a protected method on ANOTHER instance of Person 26 | 27 | // def everyoneSayHi(): String = 28 | // this.sayHi() + s"Hi, I'm $name, and here are my parents: " + mom.sayHi() + dad.sayHi() 29 | } 30 | 31 | def main(args: Array[String]): Unit = { 32 | println(aKid.greetPolitely()) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/scala/com/rockthejvm/part2oop/AnonymousClasses.scala: -------------------------------------------------------------------------------- 1 | package com.rockthejvm.part2oop 2 | 3 | object AnonymousClasses { 4 | 5 | abstract class Animal { 6 | def eat(): Unit 7 | } 8 | 9 | // classes used for just one instance are boilerplate-y 10 | class SomeAnimal extends Animal { 11 | override def eat(): Unit = println("I'm a weird animal") 12 | } 13 | 14 | val someAnimal = new SomeAnimal 15 | 16 | val someAnimal_v2 = new Animal { // anonymous class 17 | override def eat(): Unit = println("I'm a weird animal") 18 | } 19 | 20 | /* 21 | equivalent with: 22 | 23 | class AnonymousClasses.AnonClass$1 extends Animal { 24 | override def eat(): Unit = println("I'm a weird animal") 25 | } 26 | 27 | val someAnimal_v2 = new AnonymousClasses.AnonClass$1 28 | */ 29 | 30 | // works for classes (abstract or not) + traits 31 | class Person(name: String) { 32 | def sayHi(): Unit = println(s"Hi, my name is $name") 33 | } 34 | 35 | val jim = new Person("Jim") { 36 | override def sayHi(): Unit = println("MY NAME IS JIM!") 37 | } 38 | 39 | def main(args: Array[String]): Unit = { 40 | 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/scala/com/rockthejvm/part2oop/CaseClasses.scala: -------------------------------------------------------------------------------- 1 | package com.rockthejvm.part2oop 2 | 3 | object CaseClasses { 4 | 5 | // lightweight data structures 6 | case class Person(name: String, age: Int) { 7 | // do some other stuff 8 | } 9 | 10 | // 1 - class args are now fields 11 | val daniel = new Person("Daniel", 99) 12 | val danielsAge = daniel.age 13 | 14 | // 2 - toString, equals and hashCode 15 | val danielToString = daniel.toString // Person("Daniel", 99) 16 | val danielDuped = new Person("Daniel", 99) 17 | val isSameDaniel = daniel == danielDuped // true 18 | 19 | // 3 - utility methods 20 | val danielYounger = daniel.copy(age = 78) // new Person("Daniel", 78) 21 | 22 | // 4 - CCs have companion objects 23 | val thePersonSingleton = Person 24 | val daniel_v2 = Person("Daniel", 99) // "constructor" 25 | 26 | // 5 - CCs are serializable 27 | // use-case: Akka 28 | 29 | // 6 - CCs have extractor patterns for PATTERN MATCHING 30 | 31 | // can't create CCs with no arg lists 32 | /* 33 | case class CCWithNoArgs { 34 | // some code 35 | } 36 | 37 | val ccna = new CCWithNoArgs 38 | val ccna_v2 = new CCWithNoArgs // all instances would be equal! 39 | */ 40 | 41 | case object UnitedKingdom { 42 | // fields and methods 43 | def name: String = "The UK of GB and NI" 44 | } 45 | 46 | case class CCWithArgListNoArgs[A]() // legal, mainly used in the context of generics 47 | 48 | /** 49 | * Exercise: use case classes for LList. 50 | */ 51 | 52 | def main(args: Array[String]): Unit = { 53 | println(daniel) 54 | println(isSameDaniel) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/scala/com/rockthejvm/part2oop/Enums.scala: -------------------------------------------------------------------------------- 1 | package com.rockthejvm.part2oop 2 | 3 | object Enums { 4 | 5 | enum Permissions { 6 | case READ, WRITE, EXECUTE, NONE 7 | 8 | // add fields/methods 9 | def openDocument(): Unit = 10 | if (this == READ) println("opening document...") 11 | else println("reading not allowed.") 12 | } 13 | 14 | val somePermissions: Permissions = Permissions.READ 15 | 16 | // constructor args 17 | enum PermissionsWithBits(bits: Int) { 18 | case READ extends PermissionsWithBits(4) // 100 19 | case WRITE extends PermissionsWithBits(2) // 010 20 | case EXECUTE extends PermissionsWithBits(1) // 001 21 | case NONE extends PermissionsWithBits(0) // 000 22 | } 23 | 24 | object PermissionsWithBits { 25 | def fromBits(bits: Int): PermissionsWithBits = // whatever 26 | PermissionsWithBits.NONE 27 | } 28 | 29 | // standard API 30 | val somePermissionsOrdinal = somePermissions.ordinal 31 | val allPermissions = PermissionsWithBits.values // array of all possible values of the enum 32 | val readPermission: Permissions = Permissions.valueOf("READ") // Permissions.READ 33 | 34 | def main(args: Array[String]): Unit = { 35 | somePermissions.openDocument() 36 | println(somePermissionsOrdinal) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/scala/com/rockthejvm/part2oop/Exceptions.scala: -------------------------------------------------------------------------------- 1 | package com.rockthejvm.part2oop 2 | 3 | object Exceptions { 4 | 5 | val aString: String = null 6 | // aString.length crashes with a NPE 7 | 8 | // 1 - throw exceptions 9 | // val aWeirdValue: Int = throw new NullPointerException // returns Nothing 10 | 11 | // Exception hieararchy: 12 | // 13 | // Throwable: 14 | // Error, e.g. SOError, OOMError 15 | // Exception, e.g. NPException, NSEException, .... 16 | 17 | def getInt(withExceptions: Boolean): Int = 18 | if (withExceptions) throw new RuntimeException("No int for you!") 19 | else 42 20 | 21 | // 2 - catch exceptions 22 | val potentialFail = try { 23 | // code that might fail 24 | getInt(true) // an Int 25 | } catch { 26 | // most specific exceptions first 27 | case e: NullPointerException => 35 28 | case e: RuntimeException => 54 // an Int 29 | // ... 30 | } finally { 31 | // executed no matter what 32 | // closing resources 33 | // Unit here 34 | } 35 | 36 | // 3 - custom exceptions 37 | class MyException extends RuntimeException { 38 | // fields or methods 39 | override def getMessage = "MY EXCEPTION" 40 | } 41 | 42 | val myException = new MyException 43 | 44 | /** 45 | * Exercises: 46 | * 47 | * 1. Crash with SOError 48 | * 2. Crash with OOMError 49 | * 3. Find an element matching a predicate in LList 50 | */ 51 | 52 | def soCrash(): Unit = { 53 | def infinite(): Int = 1 + infinite() 54 | infinite() 55 | } 56 | 57 | def oomCrash(): Unit = { 58 | def bigString(n: Int, acc: String): String = 59 | if (n == 0) acc 60 | else bigString(n - 1, acc + acc) 61 | 62 | bigString(56175363, "Scala") 63 | } 64 | 65 | 66 | def main(args: Array[String]): Unit = { 67 | println(potentialFail) 68 | // val throwingMyException = throw myException 69 | 70 | // soCrash() 71 | oomCrash() 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/main/scala/com/rockthejvm/part2oop/Generics.scala: -------------------------------------------------------------------------------- 1 | package com.rockthejvm.part2oop 2 | 3 | object Generics { 4 | 5 | // goal: reuse code on different types 6 | 7 | // option 1: copy the code 8 | abstract class IntList { 9 | def head: Int 10 | def tail: IntList 11 | } 12 | class EmptyIntList extends IntList { 13 | override def head = throw new NoSuchElementException 14 | override def tail = throw new NoSuchElementException 15 | } 16 | class NonEmptyIntList(override val head: Int, override val tail: IntList) extends IntList 17 | 18 | abstract class StringList { 19 | def head: String 20 | def tail: StringList 21 | } 22 | class EmptyStringList extends StringList { 23 | override def head = throw new NoSuchElementException 24 | override def tail = throw new NoSuchElementException 25 | } 26 | class NonEmptyStringList(override val head: String, override val tail: StringList) extends StringList 27 | // ... and so on for all the types you want to support 28 | /* 29 | Pros: 30 | - keeps type safety: you know which list holds which kind of elements 31 | Cons: 32 | - boilerplate 33 | - unsustainable 34 | - copy/paste... really? 35 | */ 36 | 37 | // option 2: make the list hold a big, parent type 38 | abstract class GeneralList { 39 | def head: Any 40 | def tail: GeneralList 41 | } 42 | class EmptyGeneralList extends GeneralList { 43 | override def head = throw new NoSuchElementException 44 | override def tail = throw new NoSuchElementException 45 | } 46 | class NonEmptyGeneralList(override val head: Any, override val tail: GeneralList) extends GeneralList 47 | /* 48 | Pros: 49 | - no more code duplication 50 | - can support any type 51 | Cons: 52 | - lost type safety: can make no assumptions about any element or method 53 | - can now be heterogeneous: can hold cats and dogs in the same list (not funny) 54 | */ 55 | 56 | // solution: make the list generic with a type argument 57 | abstract class MyList[A] { // "generic" list; Java equivalent: abstract class MyList 58 | def head: A 59 | def tail: MyList[A] 60 | } 61 | 62 | class Empty[A] extends MyList[A] { 63 | override def head: A = throw new NoSuchElementException 64 | override def tail: MyList[A] = throw new NoSuchElementException 65 | } 66 | 67 | class NonEmpty[A](override val head: A, override val tail: MyList[A]) extends MyList[A] 68 | 69 | // can now use a concrete type argument 70 | val listOfIntegers: MyList[Int] = new NonEmpty[Int](1, new NonEmpty[Int](2, new Empty[Int])) 71 | 72 | // compiler now knows the real type of the elements 73 | val firstNumber = listOfIntegers.head 74 | val adding = firstNumber + 3 75 | 76 | // multiple type arguments 77 | trait MyMap[Key, Value] 78 | 79 | // generic methods 80 | object MyList { 81 | def from2Elements[A](elem1: A, elem2: A): MyList[A] = 82 | new NonEmpty[A](elem1, new NonEmpty[A](elem2, new Empty[A])) 83 | } 84 | 85 | // calling methods 86 | val first2Numbers = MyList.from2Elements[Int](1, 2) 87 | val first2Numbers_v2 = MyList.from2Elements(1, 2) // compiler can infer generic type from the type of the arguments 88 | val first2Numbers_v3 = new NonEmpty(1, new NonEmpty(2, new Empty)) 89 | 90 | /** 91 | * Exercise: genericize LList. 92 | */ 93 | 94 | def main(args: Array[String]): Unit = { 95 | 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/main/scala/com/rockthejvm/part2oop/Inheritance.scala: -------------------------------------------------------------------------------- 1 | package com.rockthejvm.part2oop 2 | 3 | object Inheritance { 4 | 5 | class Animal { 6 | val creatureType = "wild" 7 | def eat(): Unit = println("nomnomnom") 8 | } 9 | 10 | class Cat extends Animal { // a cat "is an" animal 11 | def crunch() = { 12 | eat() 13 | println("crunch, crunch") 14 | } 15 | } 16 | 17 | val cat = new Cat 18 | 19 | class Person(val name: String, age: Int) { 20 | def this(name: String) = this(name, 0) 21 | } 22 | 23 | class Adult(name: String, age: Int, idCard: String) extends Person(name) // must specify super-constructor 24 | 25 | // overriding 26 | class Dog extends Animal { 27 | override val creatureType = "domestic" 28 | override def eat(): Unit = println("mmm, I like this bone") 29 | 30 | // popular overridable method 31 | override def toString: String = "a dog" 32 | } 33 | 34 | // subtype polymorphism 35 | val dog: Animal = new Dog 36 | dog.eat() // the most specific method will be called 37 | 38 | // overloading vs overriding 39 | class Crocodile extends Animal { 40 | override val creatureType = "very wild" 41 | override def eat(): Unit = println("I can eat anything, I'm a croc") 42 | 43 | // overloading: multiple methods with the same name, different signatures 44 | // different signature = 45 | // different argument list (different number of args + different arg types) 46 | // + different return type (optional) 47 | def eat(animal: Animal): Unit = println("I'm eating this poor fella") 48 | def eat(dog: Dog): Unit = println("eating a dog") 49 | def eat(person: Person): Unit = println(s"I'm eating a human with the name ${person.name}") 50 | def eat(person: Person, dog: Dog): Unit = println("I'm eating a human AND the dog") 51 | // def eat(): Int = 45 // not a valid overload 52 | def eat(dog: Dog, person: Person): Unit = println("I'm eating a human AND the dog") 53 | 54 | } 55 | 56 | def main(args: Array[String]): Unit = { 57 | println(dog) // println(dog.toString) 58 | 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/main/scala/com/rockthejvm/part2oop/MethodNotations.scala: -------------------------------------------------------------------------------- 1 | package com.rockthejvm.part2oop 2 | 3 | import scala.language.postfixOps 4 | 5 | object MethodNotations { 6 | 7 | class Person(val name: String, val age: Int, favoriteMovie: String) { 8 | infix def likes(movie: String): Boolean = 9 | movie == favoriteMovie 10 | 11 | infix def +(person: Person): String = 12 | s"${this.name} is hanging out with ${person.name}" 13 | 14 | infix def +(nickname: String): Person = 15 | new Person(s"$name ($nickname)", age, favoriteMovie) 16 | 17 | infix def !!(progLanguage: String): String = 18 | s"$name wonders how can $progLanguage be so cool!" 19 | 20 | // prefix position 21 | // unary ops: -, +, ~, ! 22 | def unary_- : String = 23 | s"$name's alter ego" 24 | 25 | def unary_+ : Person = 26 | new Person(name, age + 1, favoriteMovie) 27 | 28 | def isAlive: Boolean = true 29 | 30 | def apply(): String = 31 | s"Hi, my name is $name and I really enjoy $favoriteMovie" 32 | 33 | def apply(n: Int): String = 34 | s"$name watched $favoriteMovie $n times" 35 | } 36 | 37 | val mary = new Person("Mary", 34, "Inception") 38 | val john = new Person("John", 36, "Fight Club") 39 | 40 | val negativeOne = -1 41 | 42 | /** 43 | * Exercises 44 | * - a + operator on the Person class that returns a person with a nickname 45 | * mary + "the rockstar" => new Person("Mary the rockstar", _, _) 46 | * - a UNARY + operator that increases the person's age 47 | * +mary => new Person(_, _, age + 1) 48 | * - an apply method with an int arg 49 | * mary.apply(2) => "Mary watched Inception 2 times" 50 | */ 51 | 52 | def main(args: Array[String]): Unit = { 53 | println(mary.likes("Fight Club")) 54 | // infix notation - for methods with ONE argument 55 | println(mary likes "Fight Club") // identical 56 | 57 | // "operator" = plain method 58 | println(mary + john) 59 | println(mary.+(john)) // identical 60 | println(2 + 3) 61 | println(2.+(3)) // same 62 | println(mary !! "Scala") 63 | 64 | // prefix position 65 | println(-mary) 66 | 67 | // postfix notation 68 | println(mary.isAlive) 69 | println(mary isAlive) // discouraged 70 | 71 | // apply is special 72 | println(mary.apply()) 73 | println(mary()) // same 74 | 75 | // exercises 76 | val maryWithNickname = mary + "the rockstar" 77 | println(maryWithNickname.name) 78 | 79 | val maryOlder = +mary 80 | println(maryOlder.age) // 35 81 | 82 | println(mary(10)) 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/main/scala/com/rockthejvm/part2oop/OOBasics.scala: -------------------------------------------------------------------------------- 1 | package com.rockthejvm.part2oop 2 | 3 | object OOBasics { 4 | 5 | // classes 6 | class Person(val name: String, age: Int) { // constructor signature 7 | // fields 8 | val allCaps = name.toUpperCase() 9 | 10 | // methods 11 | def greet(name: String): String = 12 | s"${this.name} says: Hi, $name" 13 | 14 | // signature differs 15 | // OVERLOADING 16 | def greet(): String = 17 | s"Hi, everyone, my name is $name" 18 | 19 | // aux constructor 20 | def this(name: String) = 21 | this(name, 0) 22 | 23 | def this() = 24 | this("Jane Doe") 25 | } 26 | 27 | val aPerson: Person = new Person("John", 26) 28 | val john = aPerson.name // class parameter != field 29 | val johnSayHiToDaniel = aPerson.greet("Daniel") 30 | val johnSaysHi = aPerson.greet() 31 | val genericPerson = new Person() 32 | 33 | def main(args: Array[String]): Unit = { 34 | val charlesDickens = new Writer("Charles", "Dickens", 1812) 35 | val charlesDickensImpostor = new Writer("Charles", "Dickens", 2021) 36 | 37 | val novel = new Novel("Great Expectations", 1861, charlesDickens) 38 | val newEdition = novel.copy(1871) 39 | 40 | println(charlesDickens.fullName) 41 | println(novel.authorAge) 42 | println(novel.isWrittenBy(charlesDickensImpostor)) // false 43 | println(novel.isWrittenBy(charlesDickens)) // true 44 | println(newEdition.authorAge) 45 | 46 | val counter = new Counter() 47 | counter.print() // 0 48 | counter.increment().print() // 1 49 | counter.increment() // always returns new instances 50 | counter.print() // 0 51 | 52 | counter.increment(10).print() // 10 53 | counter.increment(20000).print() // 20000 54 | } 55 | } 56 | 57 | /** 58 | Exercise: imagine we're creating a backend for a book publishing house. 59 | Create a Novel and a Writer class. 60 | 61 | Writer: first name, surname, year 62 | - method fullname 63 | 64 | Novel: name, year of release, author 65 | - authorAge 66 | - isWrittenBy(author) 67 | - copy (new year of release) = new instance of Novel 68 | */ 69 | 70 | class Writer(firstName: String, lastName: String, val yearOfBirth: Int) { 71 | def fullName: String = s"$firstName $lastName" 72 | } 73 | 74 | class Novel(title: String, yearOfRelease: Int, val author: Writer) { 75 | def authorAge: Int = yearOfRelease - author.yearOfBirth 76 | def isWrittenBy(author: Writer): Boolean = this.author == author 77 | def copy(newYear: Int): Novel = new Novel(title, newYear, author) 78 | } 79 | 80 | /** 81 | * Exercise #2: an immutable counter class 82 | * - constructed with an initial count 83 | * - increment/decrement => NEW instance of counter 84 | * - increment(n)/decrement(n) => NEW instance of counter 85 | * - print() 86 | * 87 | * Benefits: 88 | * + well in distributed environments 89 | * + easier to read and understand code 90 | */ 91 | 92 | class Counter(count: Int = 0) { 93 | def increment(): Counter = 94 | new Counter(count + 1) 95 | 96 | def decrement(): Counter = 97 | if (count == 0) this 98 | else new Counter(count - 1) 99 | 100 | def increment(n: Int): Counter = 101 | if (n <= 0) this 102 | else increment().increment(n - 1) // vulnerable to SOs 103 | 104 | def decrement(n: Int): Counter = 105 | if (n <= 0) this 106 | else decrement().decrement(n - 1) 107 | 108 | def print(): Unit = 109 | println(s"Current count: $count") 110 | } -------------------------------------------------------------------------------- /src/main/scala/com/rockthejvm/part2oop/Objects.scala: -------------------------------------------------------------------------------- 1 | package com.rockthejvm.part2oop 2 | 3 | object Objects { 4 | 5 | object MySingleton { // type + the only instance of this type 6 | val aField = 45 7 | def aMethod(x: Int) = x + 1 8 | } 9 | 10 | val theSingleton = MySingleton 11 | val anotherSingleton = MySingleton 12 | val isSameSingleton = theSingleton == anotherSingleton // true 13 | // objects can have fields and methods 14 | val theSingletonField = MySingleton.aField 15 | val theSingletonMethodCall = MySingleton.aMethod(99) 16 | 17 | class Person(name: String) { 18 | def sayHi(): String = s"Hi, my name is $name" 19 | } 20 | 21 | // companions = class + object with the same name in the same file 22 | object Person { // companion object 23 | // can access each other's private fields and methods 24 | val N_EYES = 2 25 | def canFly(): Boolean = false 26 | } 27 | 28 | // methods and fields in classes are used for instance-dependent functionality 29 | val mary = new Person("Mary") 30 | val mary_v2 = new Person("Mary") 31 | val marysGreeting = mary.sayHi() 32 | mary == mary 33 | 34 | // methods and fields in objects are used for instance-independent functionality - "static" 35 | val humansCanFly = Person.canFly() 36 | val nEyesHuman = Person.N_EYES 37 | 38 | // equality 39 | // 1 - equality of reference - usually defined as == 40 | val sameMary = mary eq mary_v2 // false, different instances 41 | val sameSingleton = MySingleton eq MySingleton // true 42 | // 2 - equality of "sameness" - in Java defined as .equals 43 | val sameMary_v2 = mary equals mary_v2 // false 44 | val sameMary_v3 = mary == mary_v2 // same as equals - false 45 | val sameSingleton_v2 = MySingleton == MySingleton // true 46 | 47 | // objects can extend classes 48 | object BigFoot extends Person("Big Foot") 49 | 50 | // 51 | /* 52 | Scala application: 53 | object Objects { 54 | def main(args: Array[String]): Unit = { ... } 55 | } 56 | 57 | Equivalent Java application: 58 | public class Objects { 59 | public static void main(String[] args) { ... } 60 | } 61 | */ 62 | def main(args: Array[String]): Unit = { 63 | println(sameMary_v3) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/main/scala/com/rockthejvm/part2oop/PackagesImports.scala: -------------------------------------------------------------------------------- 1 | package com.rockthejvm.part2oop 2 | 3 | import scala.collection.SortedSet 4 | 5 | // can define values and methods top-level 6 | // they will be included in a synthetic object 7 | // can be imported via an mypackage.* import 8 | val meaningOfLife = 42 9 | def computeMyLife: String = "Scala" 10 | 11 | object PackagesImports { // top-level definition 12 | // packages = form of organization of definitions, similar to a folder structure in a normal file system 13 | 14 | // fully qualified name 15 | val aList: com.rockthejvm.practice.LList[Int] = ??? // throws NotImplementedError 16 | 17 | // import 18 | import com.rockthejvm.practice.LList 19 | val anotherList: LList[Int] = ??? 20 | 21 | // importing under an alias 22 | import java.util.{List as JList} 23 | val aJavaList: JList[Int] = ??? 24 | 25 | // import everything 26 | import com.rockthejvm.practice.* 27 | val aPredicate: Cons[Int] = ??? 28 | 29 | // import multiple symbols 30 | import PhysicsConstants.{SPEED_OF_LIGHT, EARTH_GRAVITY} 31 | val c = SPEED_OF_LIGHT 32 | 33 | // import everything EXCEPT something 34 | object PlayingPhysics { 35 | import PhysicsConstants.{PLANCK as _, *} 36 | // val plank = PLANK // will not work 37 | } 38 | 39 | import com.rockthejvm.part2oop.* // import the mol and computeMyLife 40 | val mol = meaningOfLife 41 | 42 | // default imports: 43 | // scala.*, scala.Predef.*, java.lang.* 44 | 45 | // exports 46 | class PhysicsCalculator { 47 | import PhysicsConstants.* 48 | def computePhotonEnergy(wavelength: Double): Double = 49 | PLANCK / wavelength 50 | } 51 | 52 | object ScienceApp { 53 | val physicsCalculator = new PhysicsCalculator 54 | 55 | // exports create aliases for fields/methods to use locally 56 | export physicsCalculator.computePhotonEnergy 57 | 58 | def computeEquivalentMass(wavelength: Double): Double = 59 | computePhotonEnergy(wavelength) / (SPEED_OF_LIGHT * SPEED_OF_LIGHT) 60 | // ^^ the computePhotonEnergy method can be used directly (instead of physicsCalculator.computePhotonEnergy) 61 | // useful especially when these uses are repeated 62 | } 63 | 64 | def main(args: Array[String]): Unit = { 65 | // for testing 66 | } 67 | } 68 | 69 | // usually organizing "utils" and constants in separate objects 70 | object PhysicsConstants { 71 | // constants 72 | val SPEED_OF_LIGHT = 299792458 73 | val PLANCK = 6.62e-34 // scientific notation 74 | val EARTH_GRAVITY = 9.8 75 | } -------------------------------------------------------------------------------- /src/main/scala/com/rockthejvm/part2oop/PreventingInheritance.scala: -------------------------------------------------------------------------------- 1 | package com.rockthejvm.part2oop 2 | 3 | object PreventingInheritance { 4 | 5 | class Person(name: String) { 6 | final def enjoyLife(): Int = 42 // final = cannot be overridden 7 | } 8 | 9 | class Adult(name: String) extends Person(name) { 10 | // override def enjoyLife() = 999 // illegal 11 | } 12 | 13 | final class Animal // cannot be inherited 14 | // class Cat extends Animal // illegal 15 | 16 | // sealing a type hierarchy = inheritance only permitted inside this file 17 | sealed class Guitar(nStrings: Int) 18 | class ElectricGuitar(nStrings: Int) extends Guitar(nStrings) 19 | class AcousticGuitar extends Guitar(6) 20 | 21 | // no modifier = "not encouraging" inheritance 22 | // open = specifically marked for extension 23 | // not mandatory, good practice (should be accompanied by documentation on what extension implies) 24 | open class ExtensibleGuitar(nStrings: Int) 25 | 26 | def main(args: Array[String]): Unit = { 27 | 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/scala/com/rockthejvm/part3fp/AnonymousFunctions.scala: -------------------------------------------------------------------------------- 1 | package com.rockthejvm.part3fp 2 | 3 | object AnonymousFunctions { 4 | 5 | // instances of FunctionN 6 | val doubler: Int => Int = new Function1[Int, Int] { 7 | override def apply(x: Int) = x * 2 8 | } 9 | 10 | // lambdas = anonymous function instances 11 | val doubler_v2: Int => Int = (x: Int) => x * 2 // identical 12 | val adder: (Int, Int) => Int = (x: Int, y: Int) => x + y // new Function2[Int, Int, Int] { override def apply... } 13 | // zero-arg functions 14 | val justDoSomething: () => Int = () => 45 15 | val anInvocation = justDoSomething() 16 | 17 | // alt syntax with curly braces 18 | val stringToInt = { (str: String) => 19 | // implementation: code block 20 | str.toInt 21 | } 22 | 23 | // type inference 24 | val doubler_v3: Int => Int = x => x * 2 // type inferred by compiler 25 | val adder_v2: (Int, Int) => Int = (x, y) => x + y 26 | 27 | // shortest lambdas 28 | val doubler_v4: Int => Int = _ * 2 // x => x * 2 29 | val adder_v3: (Int, Int) => Int = _ + _ // (x, y) => x + y 30 | // each underscore is a different argument, you can't reuse them 31 | 32 | /** 33 | * Exercises 34 | * 1. Replace all FunctionN instantiations with lambdas in LList implementation. 35 | * 2. Rewrite the "special" adder from WhatsAFunction using lambdas. 36 | */ 37 | val superAdder = new Function1[Int, Function1[Int, Int]] { 38 | override def apply(x: Int) = new Function1[Int, Int] { 39 | override def apply(y: Int) = x + y 40 | } 41 | } 42 | 43 | val superAdder_v2 = (x: Int) => (y: Int) => x + y 44 | val adding2 = superAdder_v2(2) // (y: Int) => 2 + y 45 | val addingInvocation = adding2(43) // 45 46 | val addingInvocation_v2 = superAdder_v2(2)(43) // same 47 | 48 | def main(args: Array[String]): Unit = { 49 | println(justDoSomething) 50 | println(justDoSomething()) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/scala/com/rockthejvm/part3fp/HOFsCurrying.scala: -------------------------------------------------------------------------------- 1 | package com.rockthejvm.part3fp 2 | 3 | import scala.annotation.tailrec 4 | 5 | object HOFsCurrying { 6 | 7 | // higher order functions (HOFs) 8 | val aHof: (Int, (Int => Int)) => Int = (x, func) => x + 1 9 | val anotherHof: Int => (Int => Int) = x => (y => y + 2 * x) 10 | 11 | // quick exercise 12 | val superfunction: (Int, (String, (Int => Boolean)) => Int) => (Int => Int) = (x, func) => (y => x + y) 13 | 14 | // examples: map, flatMap, filter 15 | 16 | // more examples 17 | // f(f(f(...(f(x))) 18 | @tailrec 19 | def nTimes(f: Int => Int, n: Int, x: Int): Int = 20 | if (n <= 0) x 21 | else nTimes(f, n-1, f(x)) 22 | 23 | val plusOne = (x: Int) => x + 1 24 | val tenThousand = nTimes(plusOne, 10000, 0) 25 | 26 | /* 27 | ntv2(po, 3) = 28 | (x: Int) => ntv2(po, 2)(po(x)) = po(po(po(x))) 29 | 30 | ntv2(po, 2) = 31 | (x: Int) => ntv2(po, 1)(po(x)) = po(po(x)) 32 | 33 | ntv2(po, 1) => 34 | (x: Int) => ntv2(po, 0)(po(x)) = po(x) 35 | 36 | ntv2(po, 0) = (x: Int) => x 37 | */ 38 | def nTimes_v2(f: Int => Int, n: Int): Int => Int = 39 | if (n <= 0) (x: Int) => x 40 | else (x: Int) => nTimes_v2(f, n-1)(f(x)) 41 | 42 | val plusOneHundred = nTimes_v2(plusOne, 100) // po(po(po(po... risks SO if the arg is too big 43 | val oneHundred = plusOneHundred(0) 44 | 45 | // currying = HOFs returning function instances 46 | val superAdder: Int => Int => Int = (x: Int) => (y: Int) => x + y 47 | val add3: Int => Int = superAdder(3) 48 | val invokeSuperAdder = superAdder(3)(100) // 103 49 | 50 | // curried methods = methods with multiple arg list 51 | def curriedFormatter(fmt: String)(x: Double): String = fmt.format(x) 52 | 53 | val standardFormat: (Double => String) = curriedFormatter("%4.2f") // (x: Double) => "%4.2f".format(x) 54 | val preciseFormat: (Double => String) = curriedFormatter("%10.8f") // (x: Double) => "%10.8f".format(x) 55 | 56 | /** 57 | * 1. LList exercises 58 | * - foreach(A => Unit): Unit 59 | * [1,2,3].foreach(x => println(x)) 60 | * 61 | * - sort((A, A) => Int): LList[A] 62 | * [3,2,4,1].sort((x, y) => x - y) = [1,2,3,4] 63 | * (hint: use insertion sort) 64 | * 65 | * - zipWith[B](LList[A], (A, A) => B): LList[B] 66 | * [1,2,3].zipWith([4,5,6], x * y) => [1 * 4, 2 * 5, 3 * 6] = [4, 10, 18] 67 | * 68 | * - foldLeft[B](start: B)((A, B) => B): B 69 | * [1,2,3,4].foldLeft[Int](0)(x + y) = 10 70 | * 0 + 1 = 1 71 | * 1 + 2 = 3 72 | * 3 + 3 = 6 73 | * 6 + 4 = 10 74 | * 75 | * 2. toCurry(f: (Int, Int) => Int): Int => Int => Int 76 | * fromCurry(f: (Int => Int => Int)): (Int, Int) => Int 77 | * 78 | * 3. compose(f,g) => x => f(g(x)) 79 | * andThen(f,g) => x => g(f(x)) 80 | */ 81 | 82 | // 2 83 | def toCurry[A, B, C](f: (A, B) => C): A => B => C = 84 | x => y => f(x, y) 85 | 86 | val superAdder_v2 = toCurry[Int, Int, Int](_ + _) // same as superAdder 87 | 88 | def fromCurry[A, B, C](f: A => B => C): (A, B) => C = 89 | (x, y) => f(x)(y) 90 | 91 | val simpleAdder = fromCurry(superAdder) 92 | 93 | // 3 94 | def compose[A, B, C](f: B => C, g: A => B): A => C = 95 | x => f(g(x)) 96 | 97 | def andThen[A, B, C](f: A => B, g: B => C): A => C = 98 | x => g(f(x)) 99 | 100 | val incrementer = (x: Int) => x + 1 101 | val doubler = (x: Int) => 2 * x 102 | val composedApplication = compose(incrementer, doubler) 103 | val aSequencedApplication = andThen(incrementer, doubler) 104 | 105 | def main(args: Array[String]): Unit = { 106 | println(tenThousand) 107 | println(oneHundred) 108 | println(standardFormat(Math.PI)) 109 | println(preciseFormat(Math.PI)) 110 | println(simpleAdder(2,78)) // 80 111 | println(composedApplication(14)) // 29 = 2 * 14 + 1 112 | println(aSequencedApplication(14)) // 30 = (14 + 1) * 2 113 | 114 | 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/main/scala/com/rockthejvm/part3fp/HandlingFailure.scala: -------------------------------------------------------------------------------- 1 | package com.rockthejvm.part3fp 2 | 3 | import scala.util.{Failure, Random, Success, Try} 4 | 5 | object HandlingFailure { 6 | 7 | // Try = a potentially failed computation 8 | val aTry: Try[Int] = Try(42) 9 | val aFailedTry: Try[Int] = Try(throw new RuntimeException) 10 | 11 | // alt construction 12 | val aTry_v2: Try[Int] = Success(42) 13 | val aFailedTry_v2: Try[Int] = Failure(new RuntimeException) 14 | 15 | // main API 16 | val checkSuccess = aTry.isSuccess 17 | val checkFailure = aTry.isFailure 18 | val aChain = aFailedTry.orElse(aTry) 19 | 20 | // map, flatMap, filter, for comprehensions 21 | val anIncrementedTry = aTry.map(_ + 1) 22 | val aFlatMappedTry = aTry.flatMap(mol => Try(s"My meaning of life is $mol")) 23 | val aFilteredTry = aTry.filter(_ % 2 == 0) // Success(42) 24 | 25 | // WHY: avoid unsafe APIs which can THROW exceptions 26 | def unsafeMethod(): String = 27 | throw new RuntimeException("No string for you, buster!") 28 | 29 | // defensive: try-catch-finally 30 | val stringLengthDefensive = try { 31 | val aString = unsafeMethod() 32 | aString.length 33 | } catch { 34 | case e: RuntimeException => -1 35 | } 36 | 37 | // purely functional 38 | val stringLengthPure = Try(unsafeMethod()).map(_.length).getOrElse(-1) 39 | 40 | // DESIGN 41 | def betterUnsafeMethod(): Try[String] = Failure(new RuntimeException("No string for you, buster!")) 42 | def betterBackupMethod(): Try[String] = Success("Scala") 43 | val stringLengthPure_v2 = betterUnsafeMethod().map(_.length) 44 | val aSafeChain = betterUnsafeMethod().orElse(betterBackupMethod()).map(_.length) 45 | 46 | /** 47 | * Exercise: 48 | * obtain a connection, 49 | * then fetch the url, 50 | * then print the resulting HTML 51 | */ 52 | val host = "localhost" 53 | val port = "8081" 54 | val myDesiredURL = "rockthejvm.com/home" 55 | 56 | class Connection { 57 | val random = new Random() 58 | 59 | def get(url: String): String = { 60 | if (random.nextBoolean()) "Success" 61 | else throw new RuntimeException("Cannot fetch page right now.") 62 | } 63 | 64 | def getSafe(url: String): Try[String] = 65 | Try(get(url)) 66 | } 67 | 68 | object HttpService { 69 | val random = new Random() 70 | 71 | def getConnection(host: String, port: String): Connection = 72 | if (random.nextBoolean()) new Connection 73 | else throw new RuntimeException("Cannot access host/port combination.") 74 | 75 | def getConnectionSafe(host: String, port: String): Try[Connection] = 76 | Try(getConnection(host, port)) 77 | } 78 | 79 | // defensive style 80 | val finalHtml = try { 81 | val conn = HttpService.getConnection(host, port) 82 | val html = try { 83 | conn.get(myDesiredURL) 84 | } catch { 85 | case e: RuntimeException => s"${e.getMessage}" 86 | } 87 | } catch { 88 | case e: RuntimeException => s"${e.getMessage}" 89 | } 90 | 91 | // purely functional approach 92 | val maybeConn = Try(HttpService.getConnection(host, port)) 93 | val maybeHtml = maybeConn.flatMap(conn => Try(conn.get(myDesiredURL))) 94 | val finalResult = maybeHtml.fold(e => s"${e.getMessage}", s => s) 95 | 96 | // for-comprehension 97 | val maybeHtml_v2 = for { 98 | conn <- HttpService.getConnectionSafe(host, port) 99 | html <- conn.getSafe(myDesiredURL) 100 | } yield html 101 | val finalResult_v2 = maybeHtml.fold(e => s"${e.getMessage}", s => s) 102 | 103 | 104 | def main(args: Array[String]): Unit = { 105 | println(finalResult) 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/main/scala/com/rockthejvm/part3fp/LinearCollections.scala: -------------------------------------------------------------------------------- 1 | package com.rockthejvm.part3fp 2 | 3 | import scala.util.Random 4 | 5 | object LinearCollections { 6 | 7 | // Seq = well-defined ordering + indexing 8 | def testSeq(): Unit = { 9 | val aSequence = Seq(4,2,3,1) 10 | // main API: index an element 11 | val thirdElement = aSequence.apply(2) // element 3 12 | // map/flatMap/filter/for 13 | val anIncrementedSequence = aSequence.map(_ + 1) // [5,3,4,2] 14 | val aFlatMappedSequence = aSequence.flatMap(x => Seq(x, x + 1)) // [4,5,2,3,3,4,1,2] 15 | val aFilteredSequence = aSequence.filter(_ % 2 == 0) // [4,2] 16 | // other methods 17 | val reversed = aSequence.reverse 18 | val concatenation = aSequence ++ Seq(5,6,7) 19 | val sortedSequence = aSequence.sorted // [1,2,3,4] 20 | val sum = aSequence.foldLeft(0)(_ + _) // 10 21 | val stringRep = aSequence.mkString("[", ",", "]") 22 | 23 | println(aSequence) 24 | println(concatenation) 25 | println(sortedSequence) 26 | } 27 | 28 | // lists 29 | def testLists(): Unit = { 30 | val aList = List(1,2,3) 31 | // same API as Seq 32 | val firstElement = aList.head 33 | val rest = aList.tail 34 | // appending and prepending 35 | val aBiggerList = 0 +: aList :+ 4 36 | val prepending = 0 :: aList // :: equivalent to Cons in our LList 37 | // utility methods 38 | val scalax5 = List.fill(5)("Scala") 39 | } 40 | 41 | // ranges 42 | def testRanges(): Unit = { 43 | val aRange = 1 to 10 44 | val aNonInclusiveRange = 1 until 10 // 10 not included 45 | // same Seq API 46 | (1 to 10).foreach(_ => println("Scala")) 47 | } 48 | 49 | // arrays 50 | def testArrays(): Unit = { 51 | val anArray = Array(1,2,3,4,5,6) // int[] on the JVM 52 | // most Seq APIs 53 | // arrays are not Seqs 54 | val aSequence: Seq[Int] = anArray.toIndexedSeq 55 | // arrays are mutable 56 | anArray.update(2, 30) // no new array is allocated 57 | } 58 | 59 | // vectors = fast Seqs for a large amount of data 60 | def testVectors(): Unit = { 61 | val aVector: Vector[Int] = Vector(1,2,3,4,5,6) 62 | // the same Seq API 63 | } 64 | 65 | def smallBenchmark(): Unit = { 66 | val maxRuns = 1000 67 | val maxCapacity = 1000000 68 | 69 | def getWriteTime(collection: Seq[Int]): Double = { 70 | val random = new Random() 71 | val times = for { 72 | i <- 1 to maxRuns 73 | } yield { 74 | val index = random.nextInt(maxCapacity) 75 | val element = random.nextInt() 76 | 77 | val currentTime = System.nanoTime() 78 | val updatedCollection = collection.updated(index, element) 79 | System.nanoTime() - currentTime 80 | } 81 | 82 | // compute average 83 | times.sum * 1.0 / maxRuns 84 | } 85 | 86 | val numbersList = (1 to maxCapacity).toList 87 | val numbersVector = (1 to maxCapacity).toVector 88 | 89 | println(getWriteTime(numbersList)) 90 | println(getWriteTime(numbersVector)) 91 | } 92 | 93 | // sets 94 | def testSets(): Unit = { 95 | val aSet = Set(1,2,3,4,5,4) // no ordering guaranteed 96 | // equals + hashCode = hash set 97 | // main API: test if in the set 98 | val contains3 = aSet.contains(3) // true 99 | val contains3_v2 = aSet(3) // same: true 100 | // adding/removing 101 | val aBiggerSet = aSet + 4 // [1,2,3,4,5] 102 | val aSmallerSet = aSet - 4 // [1,2,3,5] 103 | // concatenation == union 104 | val anotherSet = Set(4,5,6,7,8) 105 | val muchBiggerSet = aSet.union(anotherSet) 106 | val muchBiggerSet_v2 = aSet ++ anotherSet // same 107 | val muchBiggerSet_v3 = aSet | anotherSet // same 108 | // difference 109 | val aDiffSet = aSet.diff(anotherSet) 110 | val aDiffSet_v2 = aSet -- anotherSet // same 111 | // intersection 112 | val anIntersection = aSet.intersect(anotherSet) // [4,5] 113 | val anIntersection_v2 = aSet & anotherSet // same 114 | } 115 | 116 | def main(args: Array[String]): Unit = { 117 | smallBenchmark() 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/main/scala/com/rockthejvm/part3fp/MapFlatMapFilterFor.scala: -------------------------------------------------------------------------------- 1 | package com.rockthejvm.part3fp 2 | 3 | object MapFlatMapFilterFor { 4 | 5 | // standard list 6 | val aList = List(1,2,3) // [1] -> [2] -> [3] -> Nil // [1,2,3] 7 | val firstElement = aList.head 8 | val restOfElements = aList.tail 9 | 10 | // map 11 | val anIncrementedList = aList.map(_ + 1) 12 | 13 | // filter 14 | val onlyOddNumbers = aList.filter(_ % 2 != 0) 15 | 16 | // flatMap 17 | val toPair = (x: Int) => List(x, x + 1) 18 | val aFlatMappedList = aList.flatMap(toPair) // [1,2,2,3,3,4] 19 | 20 | // All the possible combinations of all the elements of those lists, in the format "1a - black" 21 | val numbers = List(1, 2, 3, 4) 22 | val chars = List('a', 'b', 'c', 'd') 23 | val colors = List("black", "white", "red") 24 | 25 | /* 26 | lambda = num => chars.map(char => s"$num$char") 27 | [1,2,3,4].flatMap(lambda) = ["1a", "1b", "1c", "1d", "2a", "2b", "2c", "2d", ...] 28 | lambda(1) = chars.map(char => s"1$char") = ["1a", "1b", "1c", "1d"] 29 | lambda(2) = .. = ["2a", "2b", "2c", "2d"] 30 | lambda(3) = .. 31 | lambda(4) = .. 32 | */ 33 | val combinations = numbers.withFilter(_ % 2 == 0).flatMap(number => chars.flatMap(char => colors.map(color => s"$number$char - $color"))) 34 | 35 | // for-comprehension = IDENTICAL to flatMap + map chains 36 | val combinationsFor = for { 37 | number <- numbers if number % 2 == 0 // generator 38 | char <- chars 39 | color <- colors 40 | } yield s"$number$char - $color" // an EXPRESSION 41 | 42 | // for-comprehensions with Unit 43 | // if foreach 44 | 45 | /** 46 | * Exercises 47 | * 1. LList supports for comprehensions? 48 | * 2. A small collection of AT MOST ONE element - Maybe[A] 49 | * - map 50 | * - flatMap 51 | * - filter 52 | */ 53 | import com.rockthejvm.practice.* 54 | val lSimpleNumbers: LList[Int] = Cons(1, Cons(2, Cons(3, Empty()))) 55 | // map, flatMap, filter 56 | val incrementedNumbers = lSimpleNumbers.map(_ + 1) 57 | val filteredNumbers = lSimpleNumbers.filter(_ % 2 == 0) 58 | val toPairLList: Int => LList[Int] = (x: Int) => Cons(x, Cons(x + 1, Empty())) 59 | val flatMappedNumbers = lSimpleNumbers.flatMap(toPairLList) 60 | 61 | // map + flatMap = for comprehensions 62 | val combinationNumbers = for { 63 | number <- lSimpleNumbers if number % 2 == 0 64 | char <- Cons('a', Cons('b', Cons('c', Empty()))) 65 | } yield s"$number-$char" 66 | 67 | def main(args: Array[String]): Unit = { 68 | numbers.foreach(println) 69 | for { 70 | num <- numbers 71 | } println(num) 72 | 73 | println(combinations) 74 | println(combinationsFor) 75 | 76 | println(incrementedNumbers) 77 | println(filteredNumbers) 78 | println(flatMappedNumbers) 79 | println(combinationNumbers) 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/main/scala/com/rockthejvm/part3fp/Options.scala: -------------------------------------------------------------------------------- 1 | package com.rockthejvm.part3fp 2 | 3 | import scala.util.Random 4 | 5 | object Options { 6 | 7 | // options = "collections" with at most one value 8 | val anOption: Option[Int] = Option(42) 9 | val anEmptyOption: Option[Int] = Option.empty 10 | 11 | // alt version 12 | val aPresentValue: Option[Int] = Some(4) 13 | val anEmptyOption_v2: Option[Int] = None 14 | 15 | // "standard" API 16 | val isEmpty = anOption.isEmpty 17 | val innerValue = anOption.getOrElse(90) 18 | val anotherOption = Option(46) 19 | val aChainedOption = anEmptyOption.orElse(anotherOption) 20 | 21 | // map, flatMap, filter, for 22 | val anIncrementedOption = anOption.map(_ + 1) // Some(43) 23 | val aFilteredOption = anIncrementedOption.filter(_ % 2 == 0) // None 24 | val aFlatMappedOption = anOption.flatMap(value => Option(value * 10)) // Some(420) 25 | 26 | // WHY options: work with unsafe API 27 | def unsafeMethod(): String = null 28 | def fallbackMethod(): String = "some valid result" 29 | 30 | // defensive style 31 | val stringLength = { 32 | val potentialString = unsafeMethod() 33 | if (potentialString == null) -1 34 | else potentialString.length 35 | } 36 | 37 | // option-style: no need for null checks 38 | val stringLengthOption = Option(unsafeMethod()).map(_.length) 39 | 40 | // use-case for orElse 41 | val someResult = Option(unsafeMethod()).orElse(Option(fallbackMethod())) 42 | 43 | // DESIGN 44 | def betterUnsafeMethod(): Option[String] = None 45 | def betterFallbackMethod(): Option[String] = Some("A valid result") 46 | val betterChain = betterUnsafeMethod().orElse(betterFallbackMethod()) 47 | 48 | // example: Map.get 49 | val phoneBook = Map( 50 | "Daniel" -> 1234 51 | ) 52 | val marysPhoneNumber = phoneBook.get("Mary") // None 53 | // no need to crash, check for nulls or if Mary is present in the map 54 | 55 | /** 56 | * Exercise: 57 | * Get the host and port from the config map, 58 | * try to open a connection, 59 | * print "Conn successful" 60 | * or "Conn failed" 61 | */ 62 | 63 | val config: Map[String, String] = Map( 64 | // comes from elsewhere 65 | "host" -> "176.45.32.1", 66 | "port" -> "8081" 67 | ) 68 | 69 | class Connection { 70 | def connect(): String = "Connection successful" 71 | } 72 | 73 | object Connection { 74 | val random = new Random() 75 | 76 | def apply(host: String, port: String): Option[Connection] = 77 | if (random.nextBoolean()) Some(new Connection) 78 | else None 79 | } 80 | 81 | // defensive style (in an imperative language e.g. Java) 82 | /* 83 | String host = config("host") 84 | String port = config("port") 85 | if (host != null) 86 | if (port != null) 87 | Connection conn = Connection.apply(host, port) 88 | if (conn != null) 89 | return conn.connect() 90 | // ... that's just the happy path, we need to add the rest of the branches 91 | */ 92 | 93 | // options style 94 | val host = config.get("host") 95 | val port = config.get("port") 96 | val connection = host.flatMap(h => port.flatMap(p => Connection(h, p))) 97 | val connStatus = connection.map(_.connect()) 98 | 99 | // compact 100 | val connStatus_v2 = 101 | config.get("host").flatMap(h => 102 | config.get("port").flatMap(p => 103 | Connection(h, p).map(_.connect()) 104 | ) 105 | ) 106 | 107 | // for-comprehension 108 | val connStatus_v3 = for { 109 | h <- config.get("host") 110 | p <- config.get("port") 111 | conn <- Connection(h, p) 112 | } yield conn.connect() 113 | 114 | def main(args: Array[String]): Unit = { 115 | println(connStatus.getOrElse("Failed to establish connection")) 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/main/scala/com/rockthejvm/part3fp/TuplesMaps.scala: -------------------------------------------------------------------------------- 1 | package com.rockthejvm.part3fp 2 | 3 | object TuplesMaps { 4 | 5 | // tuples = finite ordered "lists" / group of values under the same "big" value 6 | val aTuple = (2, "rock the jvm") // Tuple2[Int, String] == (Int, String) 7 | val firstField = aTuple._1 8 | val aCopiedTuple = aTuple.copy(_1 = 54) 9 | 10 | // tuples of 2 elements 11 | val aTuple_v2 = 2 -> "rock the jvm" // IDENTICAL to (2, "rock the jvm") 12 | 13 | // maps: keys -> values 14 | val aMap = Map() 15 | 16 | val phonebook: Map[String, Int] = Map( 17 | "Jim" -> 555, 18 | "Daniel" -> 789, 19 | "Jane" -> 123 20 | ).withDefaultValue(-1) 21 | 22 | // core APIs 23 | val phonebookHasDaniel = phonebook.contains("Daniel") 24 | val marysPhoneNumber = phonebook("Mary") // crash with an exception 25 | 26 | // add a pair 27 | val newPair = "Mary" -> 678 28 | val newPhonebook = phonebook + newPair // new map 29 | 30 | // remove a key 31 | val phoneBookWithoutDaniel = phonebook - "Daniel" // new map 32 | 33 | // list -> map 34 | val linearPhonebook = List( 35 | "Jim" -> 555, 36 | "Daniel" -> 789, 37 | "Jane" -> 123 38 | ) 39 | val phonebook_v2 = linearPhonebook.toMap 40 | 41 | // map -> linear collection 42 | val linearPhonebook_v2 = phonebook.toList // toSeq, toVector, toArray, toSet 43 | 44 | // map, flatMap, filter 45 | // Map("Jim" -> 123, "jiM" -> 999) => Map("JIM" -> ????) 46 | val aProcessedPhonebook = phonebook.map(pair => (pair._1.toUpperCase(), pair._2)) 47 | 48 | // filtering keys 49 | val noJs = phonebook.view.filterKeys(!_.startsWith("J")).toMap 50 | 51 | // mapping values 52 | val prefixNumbers = phonebook.view.mapValues(number => s"0255-$number").toMap 53 | 54 | // other collections can create maps 55 | val names = List("Bob", "James", "Angela", "Mary", "Daniel", "Jim") 56 | val nameGroupings = names.groupBy(name => name.charAt(0)) // Map[Char, List[String]] 57 | 58 | 59 | def main(args: Array[String]): Unit = { 60 | println(phonebook) 61 | println(phonebookHasDaniel) 62 | println(marysPhoneNumber) 63 | println(nameGroupings) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/main/scala/com/rockthejvm/part3fp/WhatsAFunction.scala: -------------------------------------------------------------------------------- 1 | package com.rockthejvm.part3fp 2 | 3 | object WhatsAFunction { 4 | 5 | // FP: functions are "first-class" citizens, i.e. work with functions like any other values 6 | // JVM was built for Java, an OO language: objects (instances of classes) are "first-class" citizens 7 | 8 | // solution: traits with apply methods 9 | trait MyFunction[A, B] { 10 | def apply(arg: A): B 11 | } 12 | 13 | val doubler = new MyFunction[Int, Int] { 14 | override def apply(arg: Int) = arg * 2 15 | } 16 | 17 | val meaningOfLife = 42 18 | val meaningDoubled = doubler(meaningOfLife) // doubler.apply(meaningOfLife) 19 | 20 | // built-in function types 21 | // Function1[ArgType, ResultType] 22 | val doublerStandard = new Function1[Int, Int] { 23 | override def apply(arg: Int) = arg * 2 24 | } 25 | val meaningDoubled_v2 = doublerStandard(meaningOfLife) 26 | 27 | // Function2[Arg1Type, Arg2Type, ResultType] 28 | val adder = new Function2[Int, Int, Int] { 29 | override def apply(a: Int, b: Int) = a + b 30 | } 31 | val anAddition = adder(2, 67) 32 | 33 | // larger example 34 | // (Int, String, Double, Boolean) => Int, aka Function4[Int, String, Double, Boolean, Int] 35 | val athreeArgFunction = new Function4[Int, String, Double, Boolean, Int] { 36 | override def apply(v1: Int, v2: String, v3: Double, v4: Boolean): Int = ??? 37 | } 38 | 39 | // all function values in Scala are instances of FunctionN traits with apply methods 40 | 41 | /** 42 | * Exercises 43 | * 1. A function which takes 2 strings and concatenates them 44 | * 2. Replace Predicate/Transformer with the appropriate function types if necessary 45 | * 3. Define a function which takes an int as argument and returns ANOTHER FUNCTION as a result. 46 | */ 47 | 48 | // 1 49 | val concatenator: (String, String) => String = new Function2[String, String, String] { 50 | override def apply(a: String, b: String) = a + b 51 | } 52 | 53 | // 2 54 | // yes: Predicate[T] equivalent with Function1[T, Boolean] aka T => Boolean 55 | // yes: Transformer[A, B] equivalent with Function1[A, B] aka A => B 56 | 57 | // 3 58 | val superAdder = new Function1[Int, Function1[Int, Int]] { 59 | override def apply(x: Int) = new Function1[Int, Int] { 60 | override def apply(y: Int) = x + y 61 | } 62 | } 63 | 64 | val adder2 = superAdder(2) 65 | val anAddition_v2 = adder2(67) // 69 66 | // currying 67 | val anAddiotion_v3 = superAdder(2)(67) 68 | 69 | // function values = instances of FunctionN 70 | // methods = invokable members of classes (concept from OOP) 71 | // function values != methods 72 | 73 | def main(args: Array[String]): Unit = { 74 | println(concatenator("I love ", "Scala")) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/main/scala/com/rockthejvm/part4power/AllThePatterns.scala: -------------------------------------------------------------------------------- 1 | package com.rockthejvm.part4power 2 | 3 | import com.rockthejvm.practice.* 4 | 5 | object AllThePatterns { 6 | 7 | object MySingleton 8 | 9 | // 1 - constants 10 | val someValue: Any = "Scala" 11 | val constants = someValue match { 12 | case 42 => "a number" 13 | case "Scala" => "THE Scala" 14 | case true => "the truth" 15 | case MySingleton => "a singleton object" 16 | } 17 | 18 | // 2 - match anything 19 | val matchAnythingVar = 2 + 3 match { 20 | case something => s"I've matched anything, it's $something" 21 | } 22 | 23 | val matchAnything = someValue match { 24 | case _ => "I can match anything at all" 25 | } 26 | 27 | // 3 - tuples 28 | val aTuple = (1,4) 29 | val matchTuple = aTuple match { 30 | case (1, somethingElse) => s"A tuple with 1 and $somethingElse" 31 | case (something, 2) => "A tuple with 2 as its second field" 32 | } 33 | 34 | // PM structures can be NESTED 35 | 36 | val nestedTuple = (1, (2, 3)) 37 | val matchNestedTuple = nestedTuple match { 38 | case (_, (2, v)) => "A nested tuple ..." 39 | } 40 | 41 | // 4 - case classes 42 | val aList: LList[Int] = Cons(1, Cons(2, Empty())) 43 | val matchList = aList match { 44 | case Empty() => "an empty list" 45 | case Cons(head, Cons(_, tail)) => s"a non-empty list starting with $head" 46 | } 47 | 48 | val anOption: Option[Int] = Option(2) 49 | val matchOption = anOption match { 50 | case None => "an empty option" 51 | case Some(value) => s"non-empty, got $value" 52 | } 53 | 54 | // 5 - list patterns 55 | val aStandardList = List(1,2,3,42) 56 | val matchStandardList = aStandardList match { 57 | case List(1, _, _, _) => "list with 4 elements, first is 1" 58 | case List(1, _*) => "list starting with 1" 59 | case List(1, 2, _) :+ 42 => "list ending in 42" 60 | case head :: tail => "deconstructed list" 61 | } 62 | 63 | // 6 - type specifiers 64 | val unknown: Any = 2 65 | val matchTyped = unknown match { 66 | case anInt: Int => s"I matched an int, I can add 2 to it: ${anInt + 2}" 67 | case aString: String => "I matched a String" 68 | case _: Double => "I matched a double I don't care about" 69 | } 70 | 71 | // 7 - name binding 72 | val bindingNames = aList match { 73 | case Cons(head, rest @ Cons(_, tail)) => s"Can use $rest" 74 | } 75 | 76 | // 8 - chained patterns 77 | val multiMatch = aList match { 78 | case Empty() | Cons(0, _) => "an empty list to me" 79 | case _ => "anything else" 80 | } 81 | 82 | // 9 - if guards 83 | val secondElementSpecial = aList match { 84 | case Cons(_, Cons(specialElement, _)) if specialElement > 5 => "second element is big enough" 85 | case _ => "anything else" 86 | } 87 | 88 | /** 89 | Example: does this make sense? 90 | */ 91 | val aSimpleInt = 45 92 | val isEven_bad = aSimpleInt match { 93 | case n if n % 2 == 0 => true 94 | case _ => false 95 | } 96 | // anti-pattern: convoluted, hard to read 97 | 98 | // anti-pattern 2: if (condition) true else false 99 | val isEven_bad_v2 = if (aSimpleInt % 2 == 0) true else false 100 | // better - return the boolean expression, it has everything you need! 101 | val isEven = aSimpleInt % 2 == 0 102 | 103 | /** 104 | * Exercise (trick) 105 | */ 106 | val numbers: List[Int] = List(1,2,3,4) 107 | val numbersMatch = numbers match { 108 | case listOfStrings: List[String] => "a list of strings" 109 | case listOfInts: List[Int] => "a list of numbers" 110 | } 111 | 112 | /* 113 | PM runs at runtime 114 | - reflection 115 | - generic types are erased at runtime 116 | List[String] => List 117 | List[Int] => List 118 | Function1[Int, String] => Function1 119 | etc. 120 | */ 121 | 122 | def main(args: Array[String]): Unit = { 123 | println(numbersMatch) 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/main/scala/com/rockthejvm/part4power/BracelessSyntax.scala: -------------------------------------------------------------------------------- 1 | package com.rockthejvm.part4power 2 | 3 | object BracelessSyntax { 4 | 5 | // if - expressions 6 | val anIfExpression = if (2 > 3) "bigger" else "smaller" 7 | 8 | // java-style 9 | val anIfExpression_v2 = 10 | if (2 > 3) { 11 | "bigger" 12 | } else { 13 | "smaller" 14 | } 15 | 16 | // compact 17 | val anIfExpression_v3 = 18 | if (2 > 3) "bigger" 19 | else "smaller" 20 | 21 | // scala 3 22 | val anIfExpression_v4 = 23 | if 2 > 3 then 24 | "bigger" // higher indentation than the if part 25 | else 26 | "smaller" 27 | 28 | val anIfExpression_v5 = 29 | if 2 > 3 then 30 | val result = "bigger" 31 | result 32 | else 33 | val result = "smaller" 34 | result 35 | 36 | // scala 3 one-liner 37 | val anIfExpression_v6 = if 2 > 3 then "bigger" else "smaller" 38 | 39 | // for comprehensions 40 | val aForComprehension = for { 41 | n <- List(1,2,3) 42 | s <- List("black", "white") 43 | } yield s"$n$s" 44 | 45 | // scala 3 46 | val aForComprehension_v2 = 47 | for 48 | n <- List(1,2,3) 49 | s <- List("black", "white") 50 | yield s"$n$s" 51 | 52 | // pattern matching 53 | val meaningOfLife = 42 54 | val aPatternMatch = meaningOfLife match { 55 | case 1 => "the one" 56 | case 2 => "double or nothing" 57 | case _ => "something else" 58 | } 59 | 60 | // scala 3 61 | val aPatternMatch_v2 = 62 | meaningOfLife match 63 | case 1 => "the one" 64 | case 2 => "double or nothing" 65 | case _ => "something else" 66 | 67 | // methods without braces 68 | def computeMeaningOfLife(arg: Int): Int = // significant indentation starts here - think of it like a phantom code block 69 | val partialResult = 40 70 | 71 | 72 | 73 | 74 | 75 | partialResult + 2 // still part of the method implementation! 76 | 77 | // class definition with significant indentation (same for traits, objects, enums etc) 78 | class Animal: // compiler expects the body of Animal 79 | def eat(): Unit = 80 | println("I'm eating") 81 | end eat 82 | 83 | def grow(): Unit = 84 | println("I'm growing") 85 | 86 | // 3000 more lines of code 87 | end Animal // if, match, for, methods, classes, traits, enums, objects 88 | 89 | // anonymous classes 90 | val aSpecialAnimal = new Animal: 91 | override def eat(): Unit = println("I'm special") 92 | 93 | // indentation = strictly larger indentation 94 | // 3 spaces + 2 tabs > 2 spaces + 2 tabs 95 | // 3 spaces + 2 tabs > 3 spaces + 1 tab 96 | // 3 tabs + 2 spaces ??? 2 tabs + 3 spaces 97 | 98 | 99 | def main(args: Array[String]): Unit = { 100 | println(computeMeaningOfLife(78)) 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/main/scala/com/rockthejvm/part4power/ImperativeProgramming.scala: -------------------------------------------------------------------------------- 1 | package com.rockthejvm.part4power 2 | 3 | object ImperativeProgramming { 4 | 5 | val meaningOfLife: Int = 42 6 | 7 | var aVariable = 99 8 | aVariable = 100 // vars can be reassigned 9 | // aVariable = "Scala" // types cannot be changed 10 | 11 | // modify a variable in place 12 | aVariable += 10 // aVariable = aVariable + 10 13 | 14 | // increment/decrement operators don't exist in Scala 15 | // aVariable++ // illegal in Scala 16 | 17 | // loops 18 | def testLoop(): Unit = { 19 | var i = 0 20 | while (i < 10) { 21 | println(s"Counter at $i") 22 | i += 1 23 | } 24 | } 25 | 26 | /* 27 | Imperative programming (loops/variables/mutable data) are not recommended: 28 | - code becomes hard to read and understand (especially in growing code bases) 29 | - vulnerable to concurrency problems (e.g. need for synchronization) 30 | 31 | Imperative programming can help 32 | - for performance-critical applications (0.1% of cases; Akka/ZIO/Cats are already quite fast) 33 | - for interactions with Java libraries (usually mutable) 34 | 35 | Using imperative programming in Scala for no good reason defeats the purpose of Scala. 36 | */ 37 | 38 | val anExpression: Unit = aVariable += 10 39 | val aLoop: Unit = while (aVariable < 130) { 40 | println("counting more") 41 | aVariable += 1 42 | } 43 | 44 | def main(args: Array[String]): Unit = { 45 | testLoop() 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/scala/com/rockthejvm/part4power/PatternMatching.scala: -------------------------------------------------------------------------------- 1 | package com.rockthejvm.part4power 2 | 3 | import scala.util.Random 4 | 5 | object PatternMatching { 6 | 7 | // switch on steroids 8 | val random = new Random() 9 | val aValue = random.nextInt(100) 10 | 11 | val description = aValue match { 12 | case 1 => "the first" 13 | case 2 => "the second" 14 | case 3 => "the third" 15 | case _ => s"something else: $aValue" 16 | } 17 | 18 | // decompose values 19 | case class Person(name: String, age: Int) 20 | val bob = Person("Bob", 16) 21 | 22 | val greeting = bob match { 23 | case Person(n, a) if a < 18 => s"Hi, my name is $n and I'm $a years old." 24 | case Person(n, a) => s"Hello there, my name is $n and I'm not allowed to say my age." 25 | case _ => "I don't know who I am." 26 | } 27 | 28 | /* 29 | Patterns are matched in order: put the most specific patterns first. 30 | What if no cases match? MatchError 31 | What's the type returned? The lowest common ancestor of all types on the RHS of each branch. 32 | */ 33 | 34 | // PM on sealed hierarchies 35 | sealed class Animal 36 | case class Dog(breed: String) extends Animal 37 | case class Cat(meowStyle: String) extends Animal 38 | 39 | val anAnimal: Animal = Dog("Terra Nova") 40 | val animalPM = anAnimal match { 41 | case Dog(someBreed) => "I've detected a dog" 42 | case Cat(meow) => "I've detected a cat" 43 | } 44 | 45 | /** 46 | * Exercise 47 | * show(Sum(Number(2), Number(3))) = "2 + 3" 48 | * show(Sum(Sum(Number(2), Number(3)), Number(4)) = "2 + 3 + 4" 49 | * show(Prod(Sum(Number(2), Number(3)), Number(4))) = "(2 + 3) * 4" 50 | * show(Sum(Prod(Number(2), Number(3)), Number(4)) = "2 * 3 + 4" 51 | */ 52 | sealed trait Expr 53 | case class Number(n: Int) extends Expr 54 | case class Sum(e1: Expr, e2: Expr) extends Expr 55 | case class Prod(e1: Expr, e2: Expr) extends Expr 56 | 57 | def show(expr: Expr): String = expr match { 58 | case Number(n) => s"$n" 59 | case Sum(left, right) => show(left) + " + " + show(right) 60 | case Prod(left, right) => { 61 | def maybeShowParentheses(exp: Expr) = exp match { 62 | case Prod(_, _) => show(exp) 63 | case Number(_) => show(exp) 64 | case Sum(_, _) => s"(${show(exp)})" 65 | } 66 | 67 | maybeShowParentheses(left) + " * " + maybeShowParentheses(right) 68 | } 69 | } 70 | 71 | def main(args: Array[String]): Unit = { 72 | println(description) 73 | println(greeting) 74 | 75 | println(show(Sum(Number(2), Number(3)))) 76 | println(show(Sum(Sum(Number(2), Number(3)), Number(4)))) 77 | println(show(Prod(Sum(Number(2), Number(3)), Number(4)))) 78 | println(show(Sum(Prod(Number(2), Number(3)), Number(4)))) 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/main/scala/com/rockthejvm/part4power/PatternsEverywhere.scala: -------------------------------------------------------------------------------- 1 | package com.rockthejvm.part4power 2 | 3 | object PatternsEverywhere { 4 | 5 | // big idea #1: catches are actually MATCHES 6 | val potentialFailure = try { 7 | // code 8 | } catch { 9 | case e: RuntimeException => "runtime ex" 10 | case npe: NullPointerException => "npe" 11 | case _ => "some other exception" 12 | } 13 | 14 | /* 15 | try { .. code } 16 | catch (e) { 17 | e match { 18 | case e: RuntimeException => "runtime ex" 19 | case npe: NullPointerException => "npe" 20 | case _ => "some other exception" 21 | } 22 | } 23 | */ 24 | 25 | // big idea #2: for comprehensions (generators) are based on PM 26 | val aList = List(1,2,3,4) 27 | val evenNumbers = for { 28 | n <- aList if n % 2 == 0 29 | } yield 10 * n 30 | 31 | val tuples = List((1,2), (3,4)) 32 | val filterTuples = for { 33 | (first, second) <- tuples if first < 3 34 | } yield second * 100 35 | 36 | // big idea #3: new syntax (python-like) 37 | val aTuple = (1,2,3) 38 | val (a, b, c) = aTuple 39 | 40 | val head :: tail = tuples 41 | 42 | def main(args: Array[String]): Unit = { 43 | 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/scala/com/rockthejvm/playground/Playground.scala: -------------------------------------------------------------------------------- 1 | package com.rockthejvm.playground 2 | 3 | object Playground { 4 | def main(args: Array[String]): Unit = { 5 | println("Running Scala 3! I can't wait to learn Scala in this course...") 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/main/scala/com/rockthejvm/practice/LList.scala: -------------------------------------------------------------------------------- 1 | package com.rockthejvm.practice 2 | 3 | import scala.annotation.tailrec 4 | 5 | // singly linked list 6 | // [1,2,3] = [1] -> [2] -> [3] -> [] 7 | abstract class LList[A] { 8 | def head: A 9 | def tail: LList[A] 10 | def isEmpty: Boolean 11 | def add(element: A): LList[A] = Cons(element, this) 12 | 13 | // concatenation 14 | infix def ++(anotherList: LList[A]): LList[A] 15 | 16 | def map[B](transformer: A => B): LList[B] 17 | def filter(predicate: A => Boolean): LList[A] 18 | def withFilter(predicate: A => Boolean): LList[A] = filter(predicate) 19 | def flatMap[B](transformer: A => LList[B]): LList[B] 20 | 21 | // host and curries exercise 22 | def foreach(f: A => Unit): Unit 23 | def sort(compare: (A, A) => Int): LList[A] 24 | def zipWith[B, T](list: LList[T], zip: (A, T) => B): LList[B] 25 | def foldLeft[B](start: B)(operator: (B, A) => B): B 26 | } 27 | 28 | case class Empty[A]() extends LList[A] { 29 | override def head: A = throw new NoSuchElementException 30 | override def tail: LList[A] = throw new NoSuchElementException 31 | override def isEmpty = true 32 | override def toString = "[]" 33 | 34 | override infix def ++(anotherList: LList[A]): LList[A] = anotherList 35 | 36 | override def map[B](transformer: A => B): LList[B] = Empty() 37 | override def filter(predicate: A => Boolean): LList[A] = this 38 | override def flatMap[B](transformer: A => LList[B]): LList[B] = Empty() 39 | 40 | // HOFs exercises 41 | override def foreach(f: A => Unit): Unit = () 42 | override def sort(compare: (A, A) => Int) = this 43 | override def zipWith[B, T](list: LList[T], zip: (A, T) => B) = 44 | if (!list.isEmpty) throw new IllegalArgumentException("Zipping lists of nonequal length") 45 | else Empty() 46 | override def foldLeft[B](start: B)(operator: (B, A) => B) = start 47 | } 48 | 49 | case class Cons[A](override val head: A, override val tail: LList[A]) extends LList[A] { 50 | override def isEmpty: Boolean = false 51 | override def toString = { 52 | @tailrec 53 | def concatenateElements(remainder: LList[A], acc: String): String = 54 | if (remainder.isEmpty) acc 55 | else concatenateElements(remainder.tail, s"$acc, ${remainder.head}") 56 | 57 | s"[${concatenateElements(this.tail, s"$head")}]" 58 | } 59 | 60 | /* 61 | example 62 | [1,2,3] ++ [4,5,6] 63 | new Cons(1, [2,3] ++ [4,5,6]) = 64 | new Cons(1, new Cons(2, [3] ++ [4,5,6])) = 65 | new Cons(1, new Cons(2, new Cons(3, [] ++ [4,5,6]))) = 66 | new Cons(1, new Cons(2, new Cons(3, [4,5,6]))) = 67 | [1,2,3,4,5,6] 68 | */ 69 | override infix def ++(anotherList: LList[A]): LList[A] = 70 | Cons(head, tail ++ anotherList) 71 | 72 | /* 73 | example 74 | [1,2,3].map(n * 2) = 75 | new Cons(2, [2,3].map(n * 2)) = 76 | new Cons(2, new Cons(4, [3].map(n * 2))) = 77 | new Cons(2, new Cons(4, new Cons(6, [].map(n * 2)))) = 78 | new Cons(2, new Cons(4, new Cons(6, [])))) = 79 | [2,4,6] 80 | */ 81 | override def map[B](transformer: A => B): LList[B] = 82 | Cons(transformer(head), tail.map(transformer)) 83 | 84 | /* 85 | example 86 | [1,2,3].filter(n % 2 == 0) = 87 | [2,3].filter(n % 2 == 0) = 88 | new Cons(2, [3].filter(n % 2 == 0)) = 89 | new Cons(2, [].filter(n % 2 == 0)) = 90 | new Cons(2, []) = 91 | [2] 92 | */ 93 | override def filter(predicate: A => Boolean): LList[A] = 94 | if (predicate(head)) Cons(head, tail.filter(predicate)) 95 | else tail.filter(predicate) 96 | 97 | /* 98 | [1,2,3].flatMap(n => [n, n + 1]) = 99 | [1,2] ++ [2,3].flatMap(trans) = 100 | [1,2] ++ [2,3] ++ [3].flatMap(trans) = 101 | [1,2] ++ [2,3] ++ [3,4] ++ [].flatMap(trans) = 102 | [1,2] ++ [2,3] ++ [3,4] ++ [] = 103 | [1,2,2,3,3,4] 104 | */ 105 | override def flatMap[B](transformer: A => LList[B]): LList[B] = 106 | transformer(head) ++ tail.flatMap(transformer) 107 | 108 | // HOFs exercises 109 | override def foreach(f: A => Unit): Unit = { 110 | f(head) 111 | tail.foreach(f) 112 | } 113 | 114 | override def sort(compare: (A, A) => Int) = { 115 | /* 116 | compare = x - y 117 | insert(3, [1,2,4]) = 118 | Cons(1, insert(3, [2,4])) = 119 | Cons(1, Cons(2, insert(3, [4]))) = 120 | Cons(1, Cons(2, Cons(3, [4]))) = [1,2,3,4] 121 | */ 122 | // insertion sort, O(n^2), stack recursive 123 | def insert(elem: A, sortedList: LList[A]): LList[A] = 124 | if (sortedList.isEmpty) Cons(elem, Empty()) 125 | else if (compare(elem, sortedList.head) <= 0) Cons(elem, sortedList) 126 | else Cons(sortedList.head, insert(elem, sortedList.tail)) 127 | 128 | val sortedTail = tail.sort(compare) 129 | insert(head, sortedTail) 130 | } 131 | 132 | override def zipWith[B, T](list: LList[T], zip: (A, T) => B) = 133 | if (list.isEmpty) throw new IllegalArgumentException("Zipping lists of nonequal length") 134 | else Cons(zip(head, list.head), tail.zipWith(list.tail, zip)) 135 | 136 | /* 137 | [1,2,3,4].foldLeft(0)(x + y) 138 | = [2,3,4].foldLeft(1)(x + y) 139 | = [3,4].foldLeft(3)(x + y) 140 | = [4].foldLeft(6)(x + y) 141 | = [].foldLeft(10)(x + y) 142 | = 10 143 | */ 144 | override def foldLeft[B](start: B)(operator: (B, A) => B) = 145 | tail.foldLeft(operator(start, head))(operator) 146 | 147 | } 148 | 149 | /** 150 | Exercise: LList extension 151 | 152 | 1. Generic trait Predicate[T] with a little method test(T) => Boolean 153 | 2. Generic trait Transformer[A, B] with a method transform(A) => B 154 | 3. LList: 155 | - map(transformer: Transformer[A, B]) => LList[B] 156 | - filter(predicate: Predicate[A]) => LList[A] 157 | - flatMap(transformer from A to LList[B]) => LList[B] 158 | 159 | class EvenPredicate extends Predicate[Int] 160 | class StringToIntTransformer extends Transformer[String, Int] 161 | 162 | [1,2,3].map(n * 2) = [2,4,6] 163 | [1,2,3,4].filter(n % 2 == 0) = [2,4] 164 | [1,2,3].flatMap(n => [n, n+1]) => [1,2, 2,3, 3,4] 165 | */ 166 | 167 | object LList { 168 | def find[A](list: LList[A], predicate: A => Boolean): A = { 169 | if (list.isEmpty) throw new NoSuchElementException 170 | else if (predicate(list.head)) list.head 171 | else find(list.tail, predicate) 172 | } 173 | } 174 | 175 | object LListTest { 176 | def main(args: Array[String]): Unit = { 177 | val empty = Empty[Int]() 178 | println(empty.isEmpty) 179 | println(empty) 180 | 181 | val first3Numbers = Cons(1, Cons(2, Cons(3, empty))) 182 | println(first3Numbers) 183 | 184 | val first3Numbers_v2 = empty.add(1).add(2).add(3) // [3,2,1] 185 | println(first3Numbers_v2) 186 | println(first3Numbers_v2.isEmpty) 187 | 188 | val someStrings = Cons("dog", Cons("cat", Cons("crocodile", Empty()))) 189 | println(someStrings) 190 | 191 | val evenPredicate = new Function1[Int, Boolean] { 192 | override def apply(element: Int) = 193 | element % 2 == 0 194 | } 195 | 196 | val doubler = new Function1[Int, Int] { 197 | override def apply(value: Int) = value * 2 198 | } 199 | 200 | val doublerList = new Function1[Int, LList[Int]] { 201 | override def apply(value: Int) = 202 | Cons(value, Cons(value + 1, Empty())) 203 | } 204 | 205 | // map testing 206 | val numbersDoubled = first3Numbers.map(doubler) 207 | val numbersDoubled_v2 = first3Numbers.map(x => x * 2) 208 | val numbersDoubled_v3 = first3Numbers.map(_ * 2) 209 | println(numbersDoubled) 210 | 211 | val numbersNested = first3Numbers.map(doublerList) 212 | val numbersNested_v2 = first3Numbers.map(value => Cons(value, Cons(value + 1, Empty()))) 213 | println(numbersNested) 214 | 215 | // filter testing 216 | val onlyEvenNumbers = first3Numbers.filter(evenPredicate) // [2] 217 | val onlyEvenNumbers_v2 = first3Numbers.filter(elem => elem % 2 == 0) 218 | val onlyEvenNumbers_v3 = first3Numbers.filter(_ % 2 == 0) 219 | println(onlyEvenNumbers) 220 | 221 | // test concatenation 222 | val listInBothWays = first3Numbers ++ first3Numbers_v2 223 | println(listInBothWays) 224 | 225 | // test flatMap 226 | val flattenedList = first3Numbers.flatMap(doublerList) 227 | val flattenedList_v2 = first3Numbers.flatMap(value => Cons(value, Cons(value + 1, Empty()))) 228 | println(flattenedList) 229 | 230 | // find test 231 | println(LList.find[Int](first3Numbers, _ % 2 == 0)) // 2 232 | 233 | // HOFs exercises testing 234 | first3Numbers.foreach(println) 235 | println(first3Numbers_v2.sort(_ - _)) 236 | val zippedList = first3Numbers.zipWith[String, String](someStrings, (number, string) => s"$number-$string") 237 | println(zippedList) 238 | println(first3Numbers.foldLeft(0)(_ + _)) 239 | } 240 | } 241 | 242 | 243 | ////////////////////////////////////////////////////////////////////////////////////// 244 | // The code below is not used. 245 | // These traits were replaced with function types in the "What's a Function" lesson. 246 | // Left here for posterity. 247 | ////////////////////////////////////////////////////////////////////////////////////// 248 | 249 | trait Predicate[T] { // conceptually equivalent with T => Boolean 250 | def test(element: T): Boolean 251 | } 252 | 253 | class EvenPredicate extends Predicate[Int] { 254 | override def test(element: Int) = 255 | element % 2 == 0 256 | } 257 | 258 | trait Transformer[A, B] { // conceptually equivalent with A => B 259 | def transform(value: A): B 260 | } 261 | 262 | class Doubler extends Transformer[Int, Int] { 263 | override def transform(value: Int) = value * 2 264 | } 265 | 266 | class DoublerList extends Transformer[Int, LList[Int]] { 267 | override def transform(value: Int) = 268 | Cons(value, Cons(value + 1, Empty())) 269 | } 270 | -------------------------------------------------------------------------------- /src/main/scala/com/rockthejvm/practice/Maybe.scala: -------------------------------------------------------------------------------- 1 | package com.rockthejvm.practice 2 | 3 | abstract class Maybe[A] { 4 | def map[B](f: A => B): Maybe[B] 5 | def filter(predicate: A => Boolean): Maybe[A] 6 | def flatMap[B](f: A => Maybe[B]): Maybe[B] 7 | } 8 | 9 | case class MaybeNot[A]() extends Maybe[A] { 10 | override def map[B](f: A => B): Maybe[B] = MaybeNot[B]() 11 | override def filter(predicate: A => Boolean): Maybe[A] = this 12 | override def flatMap[B](f: A => Maybe[B]): Maybe[B] = MaybeNot[B]() 13 | } 14 | 15 | case class Just[A](val value: A) extends Maybe[A] { 16 | override def map[B](f: A => B): Maybe[B] = Just(f(value)) 17 | 18 | override def filter(predicate: A => Boolean): Maybe[A] = 19 | if (predicate(value)) this 20 | else MaybeNot[A]() 21 | 22 | override def flatMap[B](f: A => Maybe[B]): Maybe[B] = f(value) 23 | } 24 | 25 | object MaybeTest { 26 | def main(args: Array[String]): Unit = { 27 | val maybeInt: Maybe[Int] = Just(3) 28 | val maybeInt2: Maybe[Int] = MaybeNot() 29 | val maybeIncrementedInt = maybeInt.map(_ + 1) 30 | val maybeIncrementedInt2 = maybeInt2.map(_ + 1) 31 | println(maybeIncrementedInt) 32 | println(maybeIncrementedInt2) 33 | 34 | val maybeFiltered = maybeInt.filter(_ % 2 == 0) 35 | println(maybeFiltered) 36 | 37 | val maybeFlatMapped = maybeInt.flatMap(number => Just(number * 3)) 38 | println(maybeFlatMapped) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/scala/com/rockthejvm/practice/TuplesMapsExercises.scala: -------------------------------------------------------------------------------- 1 | package com.rockthejvm.practice 2 | 3 | import scala.annotation.tailrec 4 | 5 | object TuplesMapsExercises { 6 | 7 | /** 8 | * Social network = Map[String, Set[String]] 9 | * Friend relationships are MUTUAL. 10 | * 11 | * - add a person to the network 12 | * - remove a person from the network 13 | * - add friend relationship 14 | * - unfriend 15 | * 16 | * - number of friends of a person 17 | * - who has the most friends 18 | * - how many people have Ngit sO friends 19 | * + if there is a social connection between two people (direct or not) 20 | * 21 | * Daniel <-> Mary <-> Jane <-> Tom 22 | */ 23 | 24 | def addPerson(network: Map[String, Set[String]], newPerson: String): Map[String, Set[String]] = 25 | network + (newPerson -> Set()) 26 | 27 | def removePerson(network: Map[String, Set[String]], person: String): Map[String, Set[String]] = 28 | (network - person).map(pair => (pair._1, pair._2 - person)) 29 | 30 | def friend(network: Map[String, Set[String]], a: String, b: String): Map[String, Set[String]] = 31 | if (!network.contains(a)) throw new IllegalArgumentException(s"The person $a is not part of the network") 32 | else if (!network.contains(b)) throw new IllegalArgumentException(s"The person $b is not part of the network") 33 | else { 34 | val friendsOfA = network(a) 35 | val friendsOfB = network(b) 36 | 37 | network + (a -> (friendsOfA + b)) + (b -> (friendsOfB + a)) 38 | } 39 | 40 | def unfriend(network: Map[String, Set[String]], a: String, b: String): Map[String, Set[String]] = 41 | if (!network.contains(a) || !network.contains(b)) network 42 | else { 43 | val friendsOfA = network(a) 44 | val friendsOfB = network(b) 45 | 46 | network + (a -> (friendsOfA - b)) + (b -> (friendsOfB - a)) 47 | } 48 | 49 | 50 | // 2 51 | def nFriends(network: Map[String, Set[String]], person: String): Int = 52 | if (!network.contains(person)) -1 53 | else network(person).size 54 | 55 | def mostFriends(network: Map[String, Set[String]]): String = 56 | if (network.isEmpty) throw new RuntimeException("Network is empty, no-one with most friends") 57 | else { 58 | /* 59 | Example breakdown: 60 | Map(Bob -> Set(Mary), Mary -> Set(Bob, Jim), Jim -> Set(Mary)) 61 | 62 | ("", -1), (Bob, [Mary]) => (Bob, 1) 63 | (Bob, 1), (Mary, [Bob, Jim]) => (Mary, 2) 64 | (Mary, 2), (Jim, [Mary]) => (Mary, 2) 65 | (Mary, 2) 66 | */ 67 | val best = network.foldLeft(("", -1)) { (currentBest, newAssociation) => 68 | // code block 69 | val currentMostPopularPerson = currentBest._1 70 | val mostFriendsSoFar = currentBest._2 71 | 72 | val newPerson = newAssociation._1 73 | val newPersonFriends = newAssociation._2.size 74 | 75 | if (mostFriendsSoFar < newPersonFriends) (newPerson, newPersonFriends) 76 | else currentBest 77 | } 78 | 79 | best._1 80 | } 81 | 82 | def nPeopleWithNoFriends(network: Map[String, Set[String]]): Int = 83 | network.count(pair => pair._2.isEmpty) 84 | 85 | def socialConnection(network: Map[String, Set[String]], a: String, b: String): Boolean = { 86 | /* 87 | Example breakdown: 88 | Map(Bob -> Set(Mary), Mary -> Set(Bob, Jim), Jim -> Set(Mary, Daniel), Daniel -> Set(Jim)) 89 | 90 | socialConnection(network, Bob, Jim) = 91 | search([Mary], [Bob])) = 92 | true 93 | 94 | socialConnection(network, Bob, Daniel) = 95 | search([Mary], [Bob]) = 96 | search([] ++ [Bob, Jim] -- [Bob], [Bob, Mary]) = 97 | search([Jim], [Bob, Mary]) = 98 | true 99 | */ 100 | // Breadth-first search 101 | @tailrec 102 | def search(discoveredPeople: Set[String], consideredPeople: Set[String]): Boolean = 103 | if (discoveredPeople.isEmpty) false 104 | else { 105 | val person = discoveredPeople.head 106 | val personsFriends = network(person) 107 | 108 | if (personsFriends.contains(b)) true 109 | else search(discoveredPeople - person ++ personsFriends -- consideredPeople, consideredPeople + person) 110 | } 111 | 112 | if (!network.contains(a) || !network.contains(b)) false 113 | else search(Set(a), Set(a)) 114 | } 115 | 116 | def main(args: Array[String]): Unit = { 117 | val empty: Map[String, Set[String]] = Map() 118 | val network = addPerson(addPerson(empty, "Bob"), "Mary") 119 | println(network) 120 | println(friend(network, "Bob", "Mary")) 121 | println(unfriend(friend(network, "Bob", "Mary"), "Bob", "Mary")) 122 | 123 | val people = addPerson(addPerson(addPerson(empty, "Bob"), "Mary"), "Jim") 124 | val simpleNet = friend(friend(people, "Bob", "Mary"), "Jim", "Mary") 125 | println(simpleNet) 126 | println(nFriends(simpleNet, "Mary")) // 2 127 | println(nFriends(simpleNet, "Bob")) // 1 128 | println(nFriends(simpleNet, "Daniel")) // -1 129 | 130 | println(mostFriends(simpleNet)) 131 | 132 | println(nPeopleWithNoFriends(addPerson(simpleNet, "Daniel"))) 133 | 134 | println(socialConnection(simpleNet, "Bob", "Jim")) // true 135 | println(socialConnection(friend(network, "Bob", "Mary"), "Bob", "Mary")) // true 136 | println(socialConnection(addPerson(simpleNet, "Daniel"), "Bob", "Daniel")) // false 137 | } 138 | } 139 | --------------------------------------------------------------------------------