├── .gitignore ├── README.md ├── build.sbt ├── project ├── build.properties └── plugins.sbt └── src ├── main └── scala │ └── com │ └── ora │ └── scalaprogrammingfundamentals │ ├── Box.scala │ ├── Collectibles.scala │ ├── Computer.scala │ ├── Introspection.scala │ └── Stamp.scala └── test └── scala └── com └── ora └── scalaprogrammingfundamentals ├── ClassesSpec.scala ├── InstancesSpec.scala ├── MethodsSpec.scala └── TraitsSpec.scala /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | ### Vim template 3 | # Swap 4 | [._]*.s[a-v][a-z] 5 | [._]*.sw[a-p] 6 | [._]s[a-v][a-z] 7 | [._]sw[a-p] 8 | 9 | # Session 10 | Session.vim 11 | 12 | # Temporary 13 | .netrwhist 14 | *~ 15 | # Auto-generated tag files 16 | tags 17 | ### JetBrains template 18 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 19 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 20 | 21 | # User-specific stuff: 22 | .idea/**/workspace.xml 23 | .idea/**/tasks.xml 24 | .idea/dictionaries 25 | 26 | # Sensitive or high-churn files: 27 | .idea/**/dataSources/ 28 | .idea/**/dataSources.ids 29 | .idea/**/dataSources.xml 30 | .idea/**/dataSources.local.xml 31 | .idea/**/sqlDataSources.xml 32 | .idea/**/dynamic.xml 33 | .idea/**/uiDesigner.xml 34 | 35 | # Hydra 36 | .idea/hydra.xml 37 | 38 | # Gradle: 39 | .idea/**/gradle.xml 40 | .idea/**/libraries 41 | 42 | # CMake 43 | cmake-build-debug/ 44 | cmake-build-release/ 45 | 46 | # Mongo Explorer plugin: 47 | .idea/**/mongoSettings.xml 48 | 49 | ## File-based project format: 50 | *.iws 51 | 52 | ## Plugin-specific files: 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 | ### SBT template 72 | # Simple Build Tool 73 | # http://www.scala-sbt.org/release/docs/Getting-Started/Directories.html#configuring-version-control 74 | 75 | dist/* 76 | target/ 77 | lib_managed/ 78 | src_managed/ 79 | project/boot/ 80 | project/plugins/project/ 81 | .history 82 | .cache 83 | .lib/ 84 | ### Emacs template 85 | # -*- mode: gitignore; -*- 86 | \#*\# 87 | /.emacs.desktop 88 | /.emacs.desktop.lock 89 | *.elc 90 | auto-save-list 91 | tramp 92 | .\#* 93 | 94 | # Org-mode 95 | .org-id-locations 96 | *_archive 97 | 98 | # flymake-mode 99 | *_flymake.* 100 | 101 | # eshell files 102 | /eshell/history 103 | /eshell/lastdir 104 | 105 | # elpa packages 106 | /elpa/ 107 | 108 | # reftex files 109 | *.rel 110 | 111 | # AUCTeX auto folder 112 | /auto/ 113 | 114 | # cask packages 115 | .cask/ 116 | dist/ 117 | 118 | # Flycheck 119 | flycheck_*.el 120 | 121 | # server auth directory 122 | /server/ 123 | 124 | # projectiles files 125 | .projectile 126 | 127 | # directory configuration 128 | .dir-locals.el 129 | ### Eclipse template 130 | 131 | .metadata 132 | bin/ 133 | tmp/ 134 | *.tmp 135 | *.bak 136 | *.swp 137 | *~.nib 138 | local.properties 139 | .settings/ 140 | .loadpath 141 | .recommenders 142 | 143 | # External tool builders 144 | .externalToolBuilders/ 145 | 146 | # Locally stored "Eclipse launch configurations" 147 | *.launch 148 | 149 | # PyDev specific (Python IDE for Eclipse) 150 | *.pydevproject 151 | 152 | # CDT-specific (C/C++ Development Tooling) 153 | .cproject 154 | 155 | # CDT- autotools 156 | .autotools 157 | 158 | # Java annotation processor (APT) 159 | .factorypath 160 | 161 | # PDT-specific (PHP Development Tools) 162 | .buildpath 163 | 164 | # sbteclipse plugin 165 | .target 166 | 167 | # Tern plugin 168 | .tern-project 169 | 170 | # TeXlipse plugin 171 | .texlipse 172 | 173 | # STS (Spring Tool Suite) 174 | .springBeans 175 | 176 | # Code Recommenders 177 | .recommenders/ 178 | 179 | # Scala IDE specific (Scala & Java development for Eclipse) 180 | .cache-main 181 | .scala_dependencies 182 | .worksheet 183 | ### Scala template 184 | *.class 185 | *.log 186 | .cache-tests 187 | .idea 188 | .project 189 | .classpath 190 | ### Ensime template 191 | # Ensime specific 192 | **/.ensime 193 | **/.ensime_cache/ 194 | **/.ensime_lucene/ 195 | 196 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # scala_core_programming_1 2 | 3 | O'Reilly Scala Programming Fundamentals: Methods, Classes, Traits 4 | -------------------------------------------------------------------------------- /build.sbt: -------------------------------------------------------------------------------- 1 | name := "scala_core_programming_1" 2 | 3 | version := "1.0-SNAPSHOT" 4 | 5 | scalaVersion := "2.12.6" 6 | 7 | libraryDependencies ++= Seq("org.scalatest" %% "scalatest" % "3.0.5") 8 | 9 | EclipseKeys.withSource := true 10 | 11 | EclipseKeys.withJavadoc := true 12 | -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.1.6 2 | -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("com.typesafe.sbteclipse" % "sbteclipse-plugin" % "5.2.4") 2 | -------------------------------------------------------------------------------- /src/main/scala/com/ora/scalaprogrammingfundamentals/Box.scala: -------------------------------------------------------------------------------- 1 | package com.ora.scalaprogrammingfundamentals 2 | 3 | class Box[A](val contents:A) { 4 | def map[B](f: A => B):Box[B] = new Box(f(contents)) 5 | } 6 | -------------------------------------------------------------------------------- /src/main/scala/com/ora/scalaprogrammingfundamentals/Collectibles.scala: -------------------------------------------------------------------------------- 1 | package com.ora.scalaprogrammingfundamentals 2 | 3 | abstract class Collectible { 4 | def year:Int 5 | } 6 | 7 | class SportsCard(val year:Int, 8 | val manufacturer:String, 9 | val playerName:String) extends Collectible 10 | 11 | class BaseballCard(year:Int, 12 | manufacturer: String, 13 | playerName:String, 14 | val league:String, 15 | val division:String) extends 16 | SportsCard(year, manufacturer, playerName) 17 | 18 | -------------------------------------------------------------------------------- /src/main/scala/com/ora/scalaprogrammingfundamentals/Computer.scala: -------------------------------------------------------------------------------- 1 | package com.ora.scalaprogrammingfundamentals 2 | 3 | case class Computer(make:String, model:String, year:Int) -------------------------------------------------------------------------------- /src/main/scala/com/ora/scalaprogrammingfundamentals/Introspection.scala: -------------------------------------------------------------------------------- 1 | package com.ora.scalaprogrammingfundamentals 2 | 3 | trait Introspection { 4 | def whoAmI_?() = s"${getClass.getSimpleName}" 5 | } 6 | -------------------------------------------------------------------------------- /src/main/scala/com/ora/scalaprogrammingfundamentals/Stamp.scala: -------------------------------------------------------------------------------- 1 | 2 | package com.ora.scalaprogrammingfundamentals 3 | 4 | import java.time.LocalDate 5 | 6 | class Stamp protected[scalaprogrammingfundamentals] 7 | (val name:String, val year:Int, currentYear:() => Int) 8 | extends Introspection { 9 | require(name.nonEmpty, "Name cannot be empty") 10 | def age = currentYear() - year 11 | } 12 | 13 | object Stamp { 14 | // static factories 15 | def apply(name:String, year:Int) = { 16 | new Stamp(name, year, () => LocalDate.now.getYear) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/test/scala/com/ora/scalaprogrammingfundamentals/ClassesSpec.scala: -------------------------------------------------------------------------------- 1 | package com.ora.scalaprogrammingfundamentals 2 | 3 | import org.scalatest.{FunSuite, Matchers} 4 | 5 | class ClassesSpec extends FunSuite with Matchers { 6 | test( 7 | """Create a class, and the class should be 8 | | instantiable with the elements, but without a val 9 | | I cannot get information. Having the ability to set 10 | | the function for date is wonderful 11 | | for unit testing. You can also set the constructor 12 | | to protected""".stripMargin) { 13 | val stamp = Stamp("Jimi Hendrix", 2014) 14 | stamp.name should be ("Jimi Hendrix") 15 | stamp.year should be (2014) 16 | stamp.age should be (4) //for a limited time only 17 | } 18 | 19 | test("""Now a unit test""".stripMargin) { 20 | val stamp = new Stamp("Jimi Hendrix", 2014, () => 2018) 21 | stamp.name should be ("Jimi Hendrix") 22 | stamp.year should be (2014) 23 | stamp.age should be (4) 24 | } 25 | 26 | test( 27 | """Case classes have automatic functionality for getters, toString, 28 | equals, hashCode, apply, 29 | and basic pattern matching""".stripMargin) { 30 | val computer = Computer("Commodore", "64", 1983) 31 | val computer2 = computer.copy(model ="128", year=1986) 32 | computer2.year should be (1986) 33 | computer.year should be (1983) 34 | } 35 | 36 | test("Preconditions can be made with require and are used in the class") { 37 | val exception = the [IllegalArgumentException] thrownBy { 38 | val stamp = Stamp("", 1776) 39 | } 40 | exception.getMessage should be ("requirement failed: Name cannot be empty") 41 | } 42 | 43 | test("Subclassing in Scala") { 44 | val baseballCard = 45 | new BaseballCard(1952, "Topps", 46 | "Mickey Mantle", "American", "Eastern") 47 | baseballCard.year should be (1952) 48 | baseballCard.manufacturer should be ("Topps") 49 | baseballCard.playerName should be ("Mickey Mantle") 50 | } 51 | 52 | test("Abstract Classes in Scala") { 53 | val baseballCard = 54 | new BaseballCard(1952, "Topps", 55 | "Mickey Mantle", "American", "Eastern") 56 | baseballCard shouldBe a [Collectible] 57 | baseballCard shouldBe a [BaseballCard] 58 | } 59 | 60 | test("Generic Classes in Scala") { 61 | val baseballCard = 62 | new BaseballCard(1952, "Topps", 63 | "Mickey Mantle", "American", "Eastern") 64 | val box = new Box(baseballCard) 65 | box.contents.year should be (1952) 66 | box.contents.playerName should be ("Mickey Mantle") 67 | } 68 | 69 | test("Generic Classes in Scala with our own map") { 70 | val baseballCard = 71 | new BaseballCard(1952, "Topps", 72 | "Mickey Mantle", "American", "Eastern") 73 | val baseballCardBox = new Box(baseballCard) 74 | val yearBox = baseballCardBox.map(bc => bc.year) 75 | yearBox.contents should be (1952) 76 | } 77 | 78 | test("""Lab: Create two classes. One Employee, and One Manager. 79 | | The Employee should have a firstName, possibly a middleName, 80 | | and possibly a last name. The operative word is possibly. 81 | | The Manager is a subtype of Employee and has the same 82 | | properties except that it also has a property 83 | | of a list of Employees. When creating an Employee, create 84 | | an API that allows for the end user to leave out the middle name. 85 | | Put the Employee and Manager in the 86 | | src/main/scala folder and in the 87 | | com.ora.scalaprogrammingfundamentals package. In this 88 | | test create two Employees and and one Manager who is charge of the 89 | | Employees, assert that the number of employees is two.""".stripMargin) { 90 | pending 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/test/scala/com/ora/scalaprogrammingfundamentals/InstancesSpec.scala: -------------------------------------------------------------------------------- 1 | package com.ora.scalaprogrammingfundamentals 2 | 3 | import org.scalatest.{FunSuite, Matchers} 4 | 5 | class InstancesSpec extends FunSuite with Matchers { 6 | 7 | test("isInstanceOf determines the instance of a type") { 8 | val result = 10.isInstanceOf[Int] 9 | result should be (true) 10 | } 11 | 12 | test("asInstanceOf converts or casts a type") { 13 | val result = 10.asInstanceOf[Byte] 14 | result should be (10.0) 15 | result shouldBe a [java.lang.Byte] 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/test/scala/com/ora/scalaprogrammingfundamentals/MethodsSpec.scala: -------------------------------------------------------------------------------- 1 | package com.ora.scalaprogrammingfundamentals 2 | 3 | import org.scalatest.{FunSuite, Matchers} 4 | 5 | class MethodsSpec extends FunSuite with Matchers { 6 | test("In review, a method is structured like the following:") { 7 | def foo(x: Int): Int = { 8 | x + 1 9 | } 10 | foo(4) should be(5) //ScalaTest assertion 11 | } 12 | 13 | test("Also a method can be inlined if there is only one statement:") { 14 | def foo(x: Int): Int = x + 1 15 | foo(4) should be(5) 16 | } 17 | 18 | test("""Methods can be embedded, in case one method is 19 | | exclusively only being used by another""".stripMargin) { 20 | def foo(x: Int, y: Int): Int = { 21 | def bar(): Int = x + y + 10 22 | bar() 23 | } 24 | foo(4, 10) should be(24) 25 | } 26 | 27 | 28 | test("""Lab: Recursion is supported just like another 29 | | language, here we will do division a long 30 | | way attempt to do 31 | | division in a recursive style, extra points 32 | | for doing it in tail recursive way""".stripMargin) { 33 | 34 | pending 35 | 36 | def divide(numerator: Int, denominator: Int): Option[Int] = ??? 37 | 38 | divide(1, 0) should be(None) 39 | divide(1, 1) should be(Some(1)) 40 | divide(4, 2) should be(Some(2)) 41 | divide(10, 2) should be(Some(5)) 42 | divide(9, 3) should be(Some(3)) 43 | divide(5, 5) should be(Some(1)) 44 | } 45 | 46 | test( 47 | """Multi-parameter lists are groups or argument lists, 48 | | the purpose are two fold: The get group like terms, and 49 | | they make it easy to be partially applied, another reason is 50 | | for implicits""".stripMargin) { 51 | def foo(x:Int)(y:Int)(a:String, b:String) = { 52 | a + (x + y) + b 53 | } 54 | 55 | foo(3)(5)("<<<", ">>>") should be ("<<<8>>>") 56 | } 57 | 58 | test("""Partial Applied Function with a multi-parameter list 59 | | can be knocked out to provide only some of the entries, entries 60 | | that you can fill in later""".stripMargin) { 61 | 62 | def foo(x:Int)(y:Int)(a:String, b:String) = { 63 | a + (x + y) + b 64 | } 65 | 66 | val fun: (String, String) => String = foo(4)(3)_ 67 | 68 | fun("$$$", "$$$") should be ("$$$7$$$") 69 | fun("***", "***") should be ("***7***") 70 | } 71 | 72 | test("""In multi-parameter lists you can use a function. Typically 73 | | the function is in the last parameter group, but it's your code, 74 | | you can put it wherever you please""".stripMargin) { 75 | def foo(x:Int, y:Int)(f:Int => String) = f(x * y) 76 | val f1 = (v1:Int) => s"The value is $v1" 77 | foo(40, 10)(f1) should be ("The value is 400") 78 | } 79 | 80 | test("""This can also be partially applied where we can defer 81 | | the function until some other time.""".stripMargin) { 82 | def foo(x:Int, y:Int)(f:Int => String) = f(x * y) 83 | def f1 = foo(3, 2)_ 84 | 85 | val result1 = f1(v1 => s"The value is $v1") 86 | val result2 = f1(v2 => "Hello" * v2) 87 | 88 | result1 should be ("The value is 6") 89 | result2 should be ("HelloHelloHelloHelloHelloHello") 90 | } 91 | 92 | test("""You can also use functions as arguments in whatever 93 | | parameter list group that you want. But being the nature of a function, 94 | | a multiline function can be a block.""".stripMargin) { 95 | 96 | def foo(x:Int, y:Int)(f:Int => String) = f(x * y) 97 | 98 | val result = foo(40, 10) { i => 99 | val add3 = i + 3 100 | s"The value is $add3" 101 | } 102 | 103 | result should be ("The value is 403") 104 | } 105 | 106 | test( 107 | """What happens if I have a function as the last group in a 108 | | multi parameter list and that function has no parameters?""" 109 | .stripMargin) { 110 | 111 | def timer[A](f:() => A) = { 112 | val startTime = System.currentTimeMillis() 113 | val result = f() 114 | val endTime = System.currentTimeMillis() 115 | (endTime - startTime, result) 116 | } 117 | 118 | val t = timer(() => { 119 | Thread.sleep(4000) 120 | 50 + 10 121 | }) 122 | 123 | val time = t._1 124 | val item = t._2 125 | 126 | time should (be >= 4000L and be <= 5000L) 127 | item should be (60) 128 | } 129 | 130 | 131 | test( 132 | """The above was ugly, so let's clean it up with 133 | | a by-name parameter!""".stripMargin) { 134 | 135 | def timer[A](f: => A) = { 136 | val startTime = System.currentTimeMillis() 137 | val result = f 138 | val endTime = System.currentTimeMillis() 139 | (endTime - startTime, result) 140 | } 141 | 142 | val t = timer { 143 | Thread.sleep(4000) 144 | 50 + 10 145 | } 146 | 147 | val time = t._1 148 | val item = t._2 149 | 150 | time should (be >= 4000L and be <= 5000L) 151 | item should be (60) 152 | } 153 | 154 | test("""Turning an method into a function""") { 155 | def foo(x:Int, y:Int, z:Int) = x + y + z 156 | val f: (Int, Int, Int) => Int = foo _ 157 | f(3, 5, 10) should be (18) 158 | } 159 | 160 | test("""Turning a method into a function selectively""") { 161 | def foo(x:Int, y:Int, z:Int) = x + y + z 162 | val f = foo(3, _:Int, 4) 163 | f(5) should be (12) 164 | } 165 | 166 | test( 167 | """Lab: Reusing a foldLeft. Fold left is a method 168 | | on a collection that reduces the elements to single element, 169 | | Look at the signature for fold left in the Scala API. 170 | | It takes two multi-parameters, a seed, and a function. 171 | | What you need to do is take the list (xs) established below, 172 | | and partially apply by providing a seed of 1 and letting the function 173 | | go unresolved. Then invoke that new function two times 174 | | One with addition and the other with multiplication""".stripMargin) { 175 | pending 176 | val xs = List(1,2,3) 177 | } 178 | 179 | test( 180 | """Repeated parameters are the equivalent of varargs in Java, they 181 | | allow additional parameters and inside the method they 182 | | are just a collection called WrappedArray""".stripMargin) { 183 | 184 | def zoom[A, B](a:A, rest:B*) = { 185 | s"a:$a, rest:$rest" 186 | } 187 | 188 | zoom(3, "Hello", "World", "Scala") should be 189 | ("a:3, rest:WrappedArray(Hello, World, Scala)") 190 | } 191 | 192 | test( 193 | """Repeated parameters can be sent a list or any other collection, 194 | | but the problem is what happens when we just send collection 195 | | it would treat it as a single unit instead you can expand the units 196 | | with a :_*""".stripMargin) { 197 | def zoom[A, B](a:A, rest:B*) = { 198 | s"a:$a, rest:$rest" 199 | } 200 | 201 | zoom(3, List("Hello", "World", "Scala")) should be ("a:3, rest:WrappedArray(List(Hello, World, Scala))") 202 | 203 | zoom(3, List("Hello", "World", "Scala"):_*) should be ("a:3, rest:List(Hello, World, Scala)") 204 | } 205 | 206 | test( 207 | """Default parameters have just methods that have a value 208 | | in case you don't have one at the moment. 209 | | Another item you'll see with this example 210 | | is the named parameter. You can set a parameter 211 | | explicitly by the name to avoid any confusion as to what you 212 | | are setting""".stripMargin) { 213 | 214 | def bar(x:Int, y:Int, a:String = "##", b:String = "##") = { 215 | a + (x + y) + b 216 | } 217 | 218 | bar(10, 20) should be ("##30##") 219 | } 220 | } 221 | -------------------------------------------------------------------------------- /src/test/scala/com/ora/scalaprogrammingfundamentals/TraitsSpec.scala: -------------------------------------------------------------------------------- 1 | package com.ora.scalaprogrammingfundamentals 2 | 3 | import org.scalatest.{FunSuite, Matchers} 4 | 5 | import scala.collection.mutable.ListBuffer 6 | 7 | class TraitsSpec extends FunSuite with Matchers { 8 | test( 9 | """A trait is analogous to an interface in Java. Classes and 10 | | objects can extend traits but traits cannot be instantiated 11 | | and therefore have no parameters""".stripMargin) { 12 | 13 | trait Vehicle { 14 | def increaseSpeed(ms: Int): Vehicle 15 | 16 | def decreaseSpeed(ms: Int): Vehicle 17 | 18 | def currentSpeedMetersPerSecond: Int 19 | } 20 | 21 | class Bicycle(val currentSpeedMetersPerSecond: Int) extends Vehicle { 22 | override def increaseSpeed(ms: Int): Vehicle = 23 | new Bicycle(currentSpeedMetersPerSecond + ms) 24 | 25 | override def decreaseSpeed(ms: Int): Vehicle = 26 | new Bicycle(currentSpeedMetersPerSecond - ms) 27 | } 28 | 29 | new Bicycle(1) 30 | .increaseSpeed(3) 31 | .decreaseSpeed(1) 32 | .currentSpeedMetersPerSecond should be(3) 33 | } 34 | 35 | test( 36 | """Just like Java 8 interfaces, you can have concrete 37 | | methods (known as default methods in Java)""".stripMargin) { 38 | 39 | trait Vehicle { 40 | def increaseSpeed(ms: Int): Vehicle 41 | 42 | def decreaseSpeed(ms: Int): Vehicle 43 | 44 | def currentSpeedMetersPerSecond: Int 45 | 46 | final def currentSpeedMilesPerHour: Double = currentSpeedMetersPerSecond * 47 | 0.000621371 48 | } 49 | 50 | class Bicycle(val currentSpeedMetersPerSecond: Int) extends Vehicle { 51 | override def increaseSpeed(ms: Int): Vehicle = 52 | new Bicycle(currentSpeedMetersPerSecond + ms) 53 | 54 | override def decreaseSpeed(ms: Int): Vehicle = 55 | new Bicycle(currentSpeedMetersPerSecond - ms) 56 | } 57 | 58 | new Bicycle(1) 59 | .increaseSpeed(3) 60 | .decreaseSpeed(1) 61 | .currentSpeedMetersPerSecond should be(3) 62 | } 63 | 64 | test("""Traits are used for mixing in functionality, 65 | | this is called a mixin. In this case, we have 66 | | a trait called Introspection (see src/main/scala). 67 | | Internally it will return the name of the class 68 | | that is mixed into it.""".stripMargin) { 69 | val stamp = Stamp("Jimi Hendrix", 2014) 70 | stamp.whoAmI_?() should be("Stamp") 71 | } 72 | 73 | test( 74 | """You can extends from a trait that was not built in to begin with, be 75 | | careful that the trait is instantiated first, and may still not 76 | | have a desired effect.""".stripMargin) { 77 | trait Logging { 78 | val loggedItems: ListBuffer[String] = ListBuffer[String]() //mutable 79 | def log(x: String): Unit = loggedItems += x 80 | def logItems: List[String] = loggedItems.toList 81 | } 82 | 83 | val a = new Object() with Logging 84 | a.log("Hello") 85 | a.log("World") 86 | a.logItems should contain inOrder("Hello", "World") 87 | } 88 | 89 | test( 90 | """The confusing thing about traits is that if you 91 | | extends from a class then extend with extends, and then use with 92 | | to list all the traits you wish to inherit, if you do extends from 93 | | a superclass then you will extends with one trait and with the 94 | | remaining traits""".stripMargin) { 95 | 96 | trait MyMixin { 97 | def bar(z:Int): Int = z + 3 98 | } 99 | class MySuperclass(val x:Int) 100 | class MySubclass(x:Int, val y:String) extends MySuperclass(x) with MyMixin 101 | 102 | val mySubclass = new MySubclass(3, "Foo") 103 | mySubclass.bar(10) should be (13) 104 | } 105 | 106 | 107 | test("""Lab: Given our bicycle example, we need to mixin 108 | | a fun factor! Create a trait called FunFactor 109 | | inside of this test! 110 | | FunFactor should have one abstract method called 111 | | funFactor that returns an Int. 112 | | Either mix in the trait into the Bicycle class 113 | | or mix it onto a already 114 | | created Bicycle instance, be sure to 115 | | have an implementation of the funFactor method""".stripMargin) { 116 | 117 | pending 118 | 119 | trait Vehicle { 120 | def increaseSpeed(ms: Int): Vehicle 121 | 122 | def decreaseSpeed(ms: Int): Vehicle 123 | 124 | def currentSpeedMetersPerSecond: Int 125 | 126 | final def currentSpeedMilesPerHour: Double = currentSpeedMetersPerSecond * 127 | 0.000621371 128 | } 129 | 130 | class Bicycle(val currentSpeedMetersPerSecond: Int) extends Vehicle { 131 | override def increaseSpeed(ms: Int): Vehicle = 132 | new Bicycle(currentSpeedMetersPerSecond + ms) 133 | 134 | override def decreaseSpeed(ms: Int): Vehicle = 135 | new Bicycle(currentSpeedMetersPerSecond - ms) 136 | } 137 | } 138 | 139 | test("A previous attendees question on mixing in a global counter!") { 140 | 141 | trait Counter { 142 | var counter = 0 143 | def incrementCounter = counter = counter + 1 144 | } 145 | 146 | class Country(name: String) { 147 | Country.incrementCounter 148 | def currentCountOfAll: Int = Country.counter 149 | } 150 | 151 | object Country extends Counter 152 | 153 | val country1 = new Country("Germany") 154 | val country2 = new Country("Zaire") 155 | val country3 = new Country("China") 156 | val country4 = new Country("India") 157 | 158 | country4.currentCountOfAll should be(4) 159 | } 160 | 161 | 162 | test( 163 | """Avoiding the diamond of death. 164 | | https://en.wikipedia.org/wiki/Multiple_inheritance#The_diamond_problem. 165 | | In Scala, since traits can inherit as a diamond shape, 166 | | there has to be a strategy. Instantiation goes from left to right 167 | | (used to be right to left), 168 | | and instantiation is marked for reuse.""".stripMargin) { 169 | 170 | var list = List[String]() 171 | 172 | trait T1 { 173 | list = list :+ "Instantiated T1" 174 | } 175 | 176 | trait T2 extends T1 { 177 | list = list :+ "Instantiated T2" 178 | } 179 | 180 | trait T3 extends T1 { 181 | list = list :+ "Instantiated T3" 182 | } 183 | 184 | trait T4 extends T1 { 185 | list = list :+ "Instantiated T4" 186 | } 187 | 188 | class C1 extends T2 with T3 with T4 { 189 | list = list :+ "Instantiated C1" 190 | } 191 | 192 | list = list :+ "Creating C1" 193 | new C1 194 | list = list :+ "Created C1" 195 | 196 | list.mkString(", ") should be( 197 | "Creating C1, Instantiated T1, Instantiated T2, Instantiated T3, Instantiated T4, Instantiated C1, Created C1") 198 | } 199 | 200 | test( 201 | """Stackable traits are traits stacked one atop another, 202 | | make sure that all overrides 203 | | are labelled, abstract override. The order of the mixins are important. 204 | | Traits on the right take effect first""".stripMargin) { 205 | 206 | abstract class IntQueue { 207 | def get(): Int 208 | 209 | def put(x: Int) 210 | } 211 | 212 | import scala.collection.mutable.ArrayBuffer 213 | 214 | class BasicIntQueue extends IntQueue { 215 | private val buf = new ArrayBuffer[Int] 216 | 217 | def get() = buf.remove(0) 218 | 219 | def put(x: Int) { 220 | buf += x 221 | } 222 | } 223 | 224 | trait Doubling extends IntQueue { 225 | abstract override def put(x: Int) { 226 | super.put(2 * x) 227 | } //abstract override is necessary to stack traits 228 | } 229 | 230 | trait Incrementing extends IntQueue { 231 | abstract override def put(x: Int) { 232 | super.put(x + 1) 233 | } 234 | } 235 | 236 | trait Filtering extends IntQueue { 237 | abstract override def put(x: Int) { 238 | if (x >= 0) super.put(x) 239 | } 240 | } 241 | 242 | val myQueue = new BasicIntQueue with Doubling with Incrementing 243 | 244 | myQueue.put(4) 245 | myQueue.get() should be(10) 246 | } 247 | 248 | test( 249 | """Self types declares that a trait must be mixed into another trait. 250 | | The relationship is the following: 251 | | 252 | | * B extends A, then B is an A. 253 | | * When you use self-types, B requires an A 254 | | 255 | | This is used for a pattern called the cake pattern, but is also a 256 | | way for the class to define the non-inheritance behaviors""" 257 | .stripMargin) { 258 | trait Moveable { 259 | def increaseSpeed(ms: Int): Moveable 260 | 261 | def decreaseSpeed(ms: Int): Moveable 262 | } 263 | 264 | trait Vehicle { 265 | self: Moveable => 266 | def make: String 267 | def model: String 268 | } 269 | 270 | class Car(val make: String, val model: String, val currentSpeed: Int) 271 | extends Vehicle with Moveable { 272 | override def increaseSpeed(ms: Int) = new Car(make, model, 273 | currentSpeed + ms) 274 | 275 | override def decreaseSpeed(ms: Int) = new Car(make, model, 276 | currentSpeed - ms) 277 | } 278 | 279 | val ford = new Car("Ford", "Fiesta", 110).decreaseSpeed(20) 280 | ford.make should be("Ford") 281 | ford.currentSpeed should be(90) 282 | } 283 | } 284 | --------------------------------------------------------------------------------