├── .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 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/.idea/codeStyles/codeStyleConfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
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 |
114 |
115 |
116 |
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 |
5 |
18 |
19 |
--------------------------------------------------------------------------------
/.idea/scala_compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/scala_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
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 |
--------------------------------------------------------------------------------