├── .editorconfig ├── .gitignore ├── .scalafmt.conf ├── .travis.yml ├── build.sbt ├── conf └── .gitkeep ├── doc └── hooks │ ├── install-hooks.sh │ └── pre-push ├── project ├── Configuration.scala ├── build.properties └── sbt-scalafmt.sbt └── src ├── README.md ├── main └── tv │ └── codely │ └── scala_intro_examples │ ├── lesson_05_ifs_for │ ├── Fridge.scala │ ├── Fryer.scala │ ├── Ingredients.scala │ ├── Sandwich.scala │ ├── SandwichMaker.scala │ ├── SandwichMakerWithFor.scala │ └── SimpleFor.sc │ ├── lesson_06_anon_fn_currying │ ├── examples.sc │ └── exercise_solutions │ │ ├── joan_miralles_logger.sc │ │ └── judit_nieto.sc │ ├── lesson_07_generics │ ├── example.sc │ └── exercise_solutions │ │ └── joan_miralles │ │ ├── BaconFryer.scala │ │ ├── EggFryer.scala │ │ ├── Fryer.scala │ │ └── Ingredients.scala │ ├── lesson_08_collections │ ├── WordAggregator.scala │ ├── WordCounter.scala │ ├── examples.sc │ └── exercise_solutions │ │ └── joan_miralles │ │ ├── WordAggregator.scala │ │ └── WordCounter.scala │ ├── lesson_09_oop │ ├── CaseClass.scala │ ├── NumberWithCompanionObject.scala │ ├── StandardClass.scala │ └── exercise_solutions │ │ └── joan_miralles_solution │ │ └── Email.scala │ ├── lesson_10_enums_vs_sealed_structures │ ├── DomainError.scala │ ├── RafaState.scala │ ├── UsageExamples.sc │ ├── VideoType.scala │ ├── WeekDay.scala │ └── joan_miralles │ │ ├── IngestibleEnumType.scala │ │ ├── IngestibleSealedType.scala │ │ └── UsageExamples.sc │ └── lesson_11_futures │ ├── 1_BasicFuturesExamples.sc │ ├── 2_ComposeFuturesExamples.sc │ ├── 3_FuturesErrorHandlingExamples.sc │ ├── 4_BlockingFuturesExamples.sc │ ├── Benchmarker.scala │ ├── BrokenFridge.scala │ ├── Fridge.scala │ ├── InDelayedMemoryFridge.scala │ ├── InMemoryFridge.scala │ └── Sandwich.scala └── test └── tv └── codely └── scala_intro_examples ├── lesson_08_collections ├── WordAggregatorSpec.scala ├── WordCounterSpec.scala └── exercise_solutions │ └── joan_miralles │ ├── WordAggregatorSpec.scala │ └── WordCounterSpec.scala └── lesson_09_oop ├── CaseClassCapabilitiesSpec.scala ├── CaseClassVisibilitiesSpec.scala ├── StandardClassVisibilitiesSpec.scala └── exercise_solutions └── joan_miralles_solution └── EmailTest.scala /.editorconfig: -------------------------------------------------------------------------------- 1 | ; This file is for unifying the coding style for different editors and IDEs. 2 | ; More information at http://editorconfig.org 3 | 4 | root = true 5 | 6 | [*] 7 | charset = utf-8 8 | end_of_line = lf 9 | insert_final_newline = true 10 | trim_trailing_whitespace = true 11 | 12 | [*.scala] 13 | indent_size = 2 14 | indent_style = space 15 | 16 | [*.md] 17 | trim_trailing_whitespace = false 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | boot/ 3 | lib_managed/ 4 | src_managed/ 5 | project/plugins/project/ -------------------------------------------------------------------------------- /.scalafmt.conf: -------------------------------------------------------------------------------- 1 | style = default 2 | 3 | align = true 4 | 5 | maxColumn = 120 6 | 7 | project.excludeFilters = ["target/"] 8 | 9 | # Only format files tracked by git. 10 | project.git = true 11 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: scala 2 | 3 | scala: 4 | - 2.12.3 5 | 6 | jdk: 7 | - openjdk8 8 | 9 | script: 10 | ## This runs the template with the default parameters, and runs test within the templated app. 11 | - sbt -Dfile.encoding=UTF8 -J-XX:ReservedCodeCacheSize=256M test 12 | -------------------------------------------------------------------------------- /build.sbt: -------------------------------------------------------------------------------- 1 | /** ********* PROJECT INFO ******************/ 2 | name := "Scala intro examples" 3 | version := "1.0" 4 | 5 | /** ********* PROJECT SETTINGS ******************/ 6 | Configuration.settings 7 | 8 | /** ********* PROD DEPENDENCIES *****************/ 9 | libraryDependencies ++= Seq( 10 | "com.github.nscala-time" %% "nscala-time" % "2.18.0", 11 | "com.lihaoyi" %% "pprint" % "0.5.3" 12 | ) 13 | 14 | /** ********* TEST DEPENDENCIES *****************/ 15 | libraryDependencies ++= Seq( 16 | "org.scalatest" %% "scalatest" % "3.0.4" % Test, 17 | "org.scalamock" %% "scalamock" % "4.0.0" % Test 18 | ) 19 | 20 | /** ********* COMMANDS ALIASES ******************/ 21 | addCommandAlias("t", "test") 22 | addCommandAlias("to", "testOnly") 23 | addCommandAlias("tq", "testQuick") 24 | addCommandAlias("tsf", "testShowFailed") 25 | 26 | addCommandAlias("c", "compile") 27 | addCommandAlias("tc", "test:compile") 28 | 29 | addCommandAlias("f", "scalafmt") // Format all files according to ScalaFmt 30 | addCommandAlias("ft", "scalafmtTest") // Test if all files are formatted according to ScalaFmt 31 | 32 | addCommandAlias("prep", ";c;tc;ft") // All the needed tasks before running the test 33 | -------------------------------------------------------------------------------- /conf/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodelyTV/scala-examples/17098c9f1c750256cd6bccfad76f28f14b9c73e9/conf/.gitkeep -------------------------------------------------------------------------------- /doc/hooks/install-hooks.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | cd "$(dirname "$0")/../.." 4 | 5 | rm -rf .git/hooks 6 | 7 | ln -s ../doc/hooks .git/hooks 8 | sudo chmod -R 777 doc/hooks/* 9 | -------------------------------------------------------------------------------- /doc/hooks/pre-push: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Checks if locally staged changes are formatted properly ignoring non-staged changes. 4 | # Install it with the `install-hooks.sh` script 5 | # Based on: https://gist.github.com/cvogt/2676ed6c6d1abafa3d6a 6 | 7 | PATH=$PATH:/usr/local/bin:/usr/local/sbin 8 | 9 | echo "" 10 | echo "Running pre-push hook… (you can omit this with --no-verify, but don't)" 11 | 12 | echo "* Moving to the project directory…" 13 | _DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) 14 | DIR=$( echo $_DIR | sed 's/\/.git\/hooks$//' ) 15 | 16 | echo "* Stashing non-staged changes so we avoid checking them…" 17 | git diff --quiet 18 | hadNoNonStagedChanges=$? 19 | 20 | if ! [ $hadNoNonStagedChanges -eq 0 ] 21 | then 22 | git stash --keep-index -u > /dev/null 23 | fi 24 | 25 | echo "* Checking pre push conditions ('prep' SBT task)…" 26 | sbt prep > /dev/null 27 | canPush=$? 28 | 29 | if [ $canPush -ne 0 ] 30 | then 31 | echo " [KO] Error :(" 32 | fi 33 | 34 | echo "* Applying the stash with the non-staged changes…" 35 | if ! [ $hadNoNonStagedChanges -eq 0 ] 36 | then 37 | sleep 1 && git stash pop --index > /dev/null & # sleep because otherwise commit fails when this leads to a merge conflict 38 | fi 39 | 40 | # Final result 41 | echo "" 42 | 43 | if [ $canPush -eq 0 ] 44 | then 45 | echo "[OK] Your code will be pushed young Padawan" 46 | exit 0 47 | else 48 | echo "[KO] Cancelling push due to test code style error (run 'sbt prep' for more information)" 49 | exit 1 50 | fi 51 | -------------------------------------------------------------------------------- /project/Configuration.scala: -------------------------------------------------------------------------------- 1 | import sbt.{Tests, _} 2 | import sbt.Keys._ 3 | 4 | object Configuration { 5 | val settings = Seq( 6 | organization := "tv.codely", 7 | scalaVersion := "2.12.3", 8 | 9 | // Custom folders path (/src/main/scala and /src/test/scala by default) 10 | scalaSource in Compile := baseDirectory.value / "/src/main", 11 | scalaSource in Test := baseDirectory.value / "/src/test", 12 | 13 | // Compiler options 14 | scalacOptions ++= Seq( 15 | "-deprecation", // Warnings deprecation 16 | "-feature", // Advise features 17 | "-unchecked", // More warnings. Strict 18 | "-language:postfixOps", // Enable postfix operation such as 3 minutes 19 | "-Xlint", // More warnings when compiling 20 | "-Ywarn-dead-code", 21 | "-Ywarn-unused", 22 | "-Ywarn-unused-import", 23 | "-Xcheckinit" // Check against early initialization 24 | ), 25 | scalacOptions in run in Compile -= "-Xcheckinit", // Remove it in production because it's expensive 26 | javaOptions += "-Duser.timezone=UTC", 27 | 28 | // Test options 29 | parallelExecution in Test := false, 30 | testForkedParallel in Test := false, 31 | fork in Test := true, 32 | testOptions in Test ++= Seq( 33 | Tests.Argument(TestFrameworks.ScalaTest, "-u", "target/test-reports"), // Save test reports 34 | Tests.Argument("-oDF") // Show full stack traces and time spent in each test 35 | ) 36 | ) 37 | } 38 | -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.0.4 2 | -------------------------------------------------------------------------------- /project/sbt-scalafmt.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("com.geirsson" % "sbt-scalafmt" % "1.3.0") 2 | -------------------------------------------------------------------------------- /src/README.md: -------------------------------------------------------------------------------- 1 | Introducción a Scala 2 | -------------------------------------------------------------------------------- /src/main/tv/codely/scala_intro_examples/lesson_05_ifs_for/Fridge.scala: -------------------------------------------------------------------------------- 1 | package tv.codely.scala_intro_examples.lesson_05_ifs_for 2 | 3 | import scala.concurrent.Future 4 | 5 | import tv.codely.scala_intro_examples.lesson_05_ifs_for.Ingredients._ 6 | 7 | final class Fridge { 8 | def takeBread(): Future[Option[Bread]] = Future.successful(Option(Bread())) 9 | 10 | def takeCheese(): Future[Option[Cheese]] = Future.successful(Option(Cheese())) 11 | 12 | def takeHam(): Future[Option[Ham]] = Future.successful(Option(Ham())) 13 | 14 | def takeEgg(): Future[Option[Egg]] = Future.successful(Option(Egg())) 15 | 16 | def takeBacon(): Future[Option[Bacon]] = Future.successful(Option(Bacon())) 17 | } 18 | -------------------------------------------------------------------------------- /src/main/tv/codely/scala_intro_examples/lesson_05_ifs_for/Fryer.scala: -------------------------------------------------------------------------------- 1 | package tv.codely.scala_intro_examples.lesson_05_ifs_for 2 | 3 | import scala.concurrent.{ExecutionContext, Future} 4 | import scala.concurrent.duration._ 5 | 6 | import tv.codely.scala_intro_examples.lesson_05_ifs_for.Ingredients.{Bacon, FriedBacon} 7 | 8 | final class Fryer(implicit ec: ExecutionContext) { 9 | private val minFryTime = 1.second 10 | 11 | def fry(bacon: Bacon): Future[FriedBacon] = Future { 12 | Thread.sleep(minFryTime.toMillis) 13 | 14 | FriedBacon(bacon) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/tv/codely/scala_intro_examples/lesson_05_ifs_for/Ingredients.scala: -------------------------------------------------------------------------------- 1 | package tv.codely.scala_intro_examples.lesson_05_ifs_for 2 | 3 | abstract class Ingredient() 4 | 5 | object Ingredients { 6 | final case class Bread() extends Ingredient 7 | final case class Cheese() extends Ingredient 8 | final case class Ham() extends Ingredient 9 | final case class Egg() extends Ingredient 10 | final case class Bacon() extends Ingredient 11 | final case class FriedBacon(bacon: Bacon) extends Ingredient 12 | } 13 | -------------------------------------------------------------------------------- /src/main/tv/codely/scala_intro_examples/lesson_05_ifs_for/Sandwich.scala: -------------------------------------------------------------------------------- 1 | package tv.codely.scala_intro_examples.lesson_05_ifs_for 2 | 3 | import scala.reflect.ClassTag 4 | 5 | object Sandwich { 6 | def apply[T: ClassTag](allIngredients: Seq[Option[Ingredient]]): Sandwich = { 7 | val availableIngredients = allIngredients.flatten 8 | 9 | Sandwich(availableIngredients) 10 | } 11 | } 12 | 13 | final case class Sandwich(ingredients: Seq[Ingredient]) 14 | -------------------------------------------------------------------------------- /src/main/tv/codely/scala_intro_examples/lesson_05_ifs_for/SandwichMaker.scala: -------------------------------------------------------------------------------- 1 | package tv.codely.scala_intro_examples.lesson_05_ifs_for 2 | 3 | import scala.concurrent.{ExecutionContext, Future} 4 | 5 | final class SandwichMaker(private val fridge: Fridge, private val fryer: Fryer)(implicit ec: ExecutionContext) { 6 | def make(): Future[Sandwich] = { 7 | val breadOptionFuture = fridge.takeBread() 8 | val cheeseOptionFuture = fridge.takeCheese() 9 | val hamOptionFuture = fridge.takeHam() 10 | val eggOptionFuture = fridge.takeEgg() 11 | val baconOptionFuture = fridge.takeBacon() 12 | 13 | breadOptionFuture.flatMap { breadOption => 14 | cheeseOptionFuture.flatMap { cheeseOption => 15 | hamOptionFuture.flatMap { hamOption => 16 | eggOptionFuture.flatMap { eggOption => 17 | baconOptionFuture.map { baconOption => 18 | Sandwich(Seq(breadOption, cheeseOption, hamOption, eggOption, baconOption)) 19 | } 20 | } 21 | } 22 | } 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/tv/codely/scala_intro_examples/lesson_05_ifs_for/SandwichMakerWithFor.scala: -------------------------------------------------------------------------------- 1 | package tv.codely.scala_intro_examples.lesson_05_ifs_for 2 | 3 | import scala.concurrent.{ExecutionContext, Future} 4 | 5 | final class SandwichMakerWithFor(private val fridge: Fridge, private val fryer: Fryer)(implicit ec: ExecutionContext) { 6 | def make(): Future[Sandwich] = 7 | for { 8 | breadOption <- fridge.takeBread() 9 | cheeseOption <- fridge.takeCheese() 10 | hamOption <- fridge.takeHam() 11 | eggOption <- fridge.takeEgg() 12 | baconOption <- fridge.takeBacon() 13 | } yield Sandwich(Seq(breadOption, cheeseOption, hamOption, eggOption, baconOption)) 14 | } 15 | -------------------------------------------------------------------------------- /src/main/tv/codely/scala_intro_examples/lesson_05_ifs_for/SimpleFor.sc: -------------------------------------------------------------------------------- 1 | val lele = if ("Codely mola".isInstanceOf[String]) 3 else 7 2 | 3 | var total = 0 4 | for (value <- 1 to 10) { 5 | total = total + value 6 | println(value) 7 | } 8 | 9 | (1 to 10).foreach(value2 => { 10 | println(value2) 11 | }) 12 | 13 | (1 to 10).foreach(println(_)) 14 | 15 | (1 to 10).foreach(println) 16 | 17 | (1 to 10) foreach println 18 | 19 | (1 to 10).sum 20 | -------------------------------------------------------------------------------- /src/main/tv/codely/scala_intro_examples/lesson_06_anon_fn_currying/examples.sc: -------------------------------------------------------------------------------- 1 | def print( 2 | value: String, 3 | printer: String => Unit 4 | ): Unit = printer(value) 5 | 6 | 7 | def printlnPrinter(value: String): Unit = println(value) 8 | val printlnPrinterVal = (value: String) => println(value) 9 | 10 | print("Javi es el peor", printlnPrinter) 11 | print("Javi es el peor", printlnPrinterVal) 12 | print("Javi es el peor", (value: String) => println(value)) 13 | 14 | def sum(a: Int)(b: Int): Int = a + b 15 | 16 | def add1(b: Int) = sum(1)(b) 17 | def add2(b: Int) = sum(2)(b) 18 | def add3(b: Int) = sum(3)(b) 19 | 20 | val add4 = sum(4)(_) 21 | 22 | add2(5) 23 | add4(6) 24 | 25 | def printZurra(printer: String => Unit)(value: String): Unit = printer(value) 26 | 27 | def printStd(value: String): Unit = printZurra(printlnPrinter)(value) 28 | 29 | printStd("De nada") 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /src/main/tv/codely/scala_intro_examples/lesson_06_anon_fn_currying/exercise_solutions/joan_miralles_logger.sc: -------------------------------------------------------------------------------- 1 | //Logger 2 | 3 | def logWithPrefix(prefix: String)(level: String)(message: String): Unit = println(s"[$prefix] $level $message") 4 | 5 | def mySolutionLog(level: String)(message: String): Unit = logWithPrefix("MY_SOLUTION")(level)(message) 6 | 7 | def debug(message: String): Unit = mySolutionLog(level = "[DEBUG]")(message) 8 | def info(message: String): Unit = mySolutionLog(level = "[INFO]")(message) 9 | def warn(message: String): Unit = mySolutionLog(level = "[WARNING]")(message) 10 | def error(message: String): Unit = mySolutionLog(level = "[ERROR]")(message) 11 | 12 | debug("I'm a debug message") 13 | info("I'm an info message") 14 | warn("I'm a warn message") 15 | error("I'm an error message") 16 | -------------------------------------------------------------------------------- /src/main/tv/codely/scala_intro_examples/lesson_06_anon_fn_currying/exercise_solutions/judit_nieto.sc: -------------------------------------------------------------------------------- 1 | // 1a versión 2 | 3 | class Logger(prefix:String){ 4 | 5 | def printLogger(message: String) = println(s"$prefix - $message") 6 | } 7 | 8 | 9 | def testingLogger() = { 10 | var logger = new Logger("MyTest") 11 | 12 | logger.printLogger("Start logging") 13 | 14 | val result = 42 15 | 16 | logger.printLogger(s"La respuesta a todo es $result") 17 | } 18 | 19 | 20 | testingLogger() 21 | 22 | 23 | // 2a versión - currying 24 | 25 | 26 | def functionCurried(head: String)(message: String) = println(s"$head - $message") 27 | 28 | def curryPrinter(message: String) = functionCurried("Cabecera ")(message) 29 | 30 | curryPrinter("y empezamos!") 31 | -------------------------------------------------------------------------------- /src/main/tv/codely/scala_intro_examples/lesson_07_generics/example.sc: -------------------------------------------------------------------------------- 1 | val intSeq = Seq(1, 2, 3) 2 | val stringSeq = Seq("uno", "dos", "tres ") 3 | 4 | case class UserId(value: String) 5 | case class User(id: UserId) 6 | 7 | trait Repository[AggregateIdType, AggregateType] { 8 | def save(aggregate: AggregateType): Unit 9 | 10 | def search(id: AggregateIdType): AggregateType 11 | } 12 | 13 | class FakeUserRepository extends Repository[UserId, User] { 14 | def save(aggregate: User): Unit = () 15 | 16 | def search(id: UserId): User = User(id) 17 | } 18 | 19 | -------------------------------------------------------------------------------- /src/main/tv/codely/scala_intro_examples/lesson_07_generics/exercise_solutions/joan_miralles/BaconFryer.scala: -------------------------------------------------------------------------------- 1 | package tv.codely.scala_intro_examples.lesson_07_generics.exercise_solutions.joan_miralles 2 | 3 | import tv.codely.scala_intro_examples.lesson_07_generics.exercise_solutions.joan_miralles.Ingredients.{ 4 | Bacon, 5 | FriedBacon 6 | } 7 | 8 | import scala.concurrent.duration._ 9 | import scala.concurrent.{ExecutionContext, Future} 10 | 11 | final class BaconFryer(implicit ec: ExecutionContext) extends Fryer[Bacon, FriedBacon] { 12 | private val minFryTime = 1.second 13 | 14 | override def fry(bacon: Bacon): Future[FriedBacon] = Future { 15 | Thread.sleep(minFryTime.toMillis) 16 | 17 | FriedBacon(bacon) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/tv/codely/scala_intro_examples/lesson_07_generics/exercise_solutions/joan_miralles/EggFryer.scala: -------------------------------------------------------------------------------- 1 | package tv.codely.scala_intro_examples.lesson_07_generics.exercise_solutions.joan_miralles 2 | 3 | import tv.codely.scala_intro_examples.lesson_07_generics.exercise_solutions.joan_miralles.Ingredients.{Egg, FriedEgg} 4 | 5 | import scala.concurrent.duration._ 6 | import scala.concurrent.{ExecutionContext, Future} 7 | 8 | class EggFryer(implicit ec: ExecutionContext) extends Fryer[Egg, FriedEgg] { 9 | private val minFryTime = 6.second 10 | 11 | override def fry(egg: Egg) = Future { 12 | Thread.sleep(minFryTime.toMillis) 13 | 14 | FriedEgg(egg) 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/main/tv/codely/scala_intro_examples/lesson_07_generics/exercise_solutions/joan_miralles/Fryer.scala: -------------------------------------------------------------------------------- 1 | package tv.codely.scala_intro_examples.lesson_07_generics.exercise_solutions.joan_miralles 2 | 3 | import scala.concurrent.Future 4 | 5 | trait Fryer[IngredientType, FriedIngredientType] { 6 | def fry(ingredientType: IngredientType): Future[FriedIngredientType] 7 | } 8 | -------------------------------------------------------------------------------- /src/main/tv/codely/scala_intro_examples/lesson_07_generics/exercise_solutions/joan_miralles/Ingredients.scala: -------------------------------------------------------------------------------- 1 | package tv.codely.scala_intro_examples.lesson_07_generics.exercise_solutions.joan_miralles 2 | 3 | abstract class Ingredient() 4 | 5 | object Ingredients { 6 | final case class Egg() extends Ingredient 7 | final case class Bacon() extends Ingredient 8 | final case class FriedBacon(bacon: Bacon) extends Ingredient 9 | final case class FriedEgg(egg: Egg) extends Ingredient 10 | } 11 | -------------------------------------------------------------------------------- /src/main/tv/codely/scala_intro_examples/lesson_08_collections/WordAggregator.scala: -------------------------------------------------------------------------------- 1 | package tv.codely.scala_intro_examples.lesson_08_collections 2 | 3 | final class WordAggregator { 4 | // @ToDo: Here you have your exercise! 5 | // @see tv.codely.scala_intro_examples.lesson_08_collections.WordAggregatorSpec 6 | def aggregateWords(text: String): Map[String, Int] = { 7 | Map.empty 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/main/tv/codely/scala_intro_examples/lesson_08_collections/WordCounter.scala: -------------------------------------------------------------------------------- 1 | package tv.codely.scala_intro_examples.lesson_08_collections 2 | 3 | final class WordCounter { 4 | // @ToDo: Here you have your exercise! 5 | // @see tv.codely.scala_intro_examples.lesson_08_collections.WordCounterSpec 6 | def countWords(text: String): Int = { 7 | 0 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/main/tv/codely/scala_intro_examples/lesson_08_collections/examples.sc: -------------------------------------------------------------------------------- 1 | import scala.collection.mutable 2 | 3 | Seq() 4 | val codelis = Seq("Javi", "Pepe") 5 | mutable.Seq(1, 2, 3) 6 | 7 | Seq[Int](456, 4234, 2476) 8 | 9 | codelis :+ "Rafa" 10 | "Rafa" +: codelis 11 | "Rafa" :+ codelis 12 | -------------------------------------------------------------------------------- /src/main/tv/codely/scala_intro_examples/lesson_08_collections/exercise_solutions/joan_miralles/WordAggregator.scala: -------------------------------------------------------------------------------- 1 | package tv.codely.scala_intro_examples.lesson_08_collections.exercise_solutions.joan_miralles 2 | 3 | final class WordAggregator { 4 | def aggregateWords(text: String): Map[String, Int] = { 5 | if (text.isEmpty) 6 | Map.empty 7 | else 8 | text.trim.split(" ").transform(_.toLowerCase) 9 | .groupBy(identity) 10 | .mapValues(_.length) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/tv/codely/scala_intro_examples/lesson_08_collections/exercise_solutions/joan_miralles/WordCounter.scala: -------------------------------------------------------------------------------- 1 | package tv.codely.scala_intro_examples.lesson_08_collections.exercise_solutions.joan_miralles 2 | 3 | final class WordCounter { 4 | def countWords(text: String): Int = { 5 | if (text.isEmpty) 6 | 0 7 | else { 8 | val words = text.trim.split(" ") 9 | words.length 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/tv/codely/scala_intro_examples/lesson_09_oop/CaseClass.scala: -------------------------------------------------------------------------------- 1 | package tv.codely.scala_intro_examples.lesson_09_oop 2 | 3 | final case class CaseClass( 4 | attributeInConstruct: String, 5 | private val privateAttributeInConstruct: String = "Some default value" 6 | ) { 7 | val attributeInBody = "public body attribute value" 8 | private val privateAttributeInBody = "private body attribute value" 9 | } 10 | -------------------------------------------------------------------------------- /src/main/tv/codely/scala_intro_examples/lesson_09_oop/NumberWithCompanionObject.scala: -------------------------------------------------------------------------------- 1 | package tv.codely.scala_intro_examples.lesson_09_oop 2 | 3 | import scala.util.Random 4 | 5 | object NumberWithCompanionObject { 6 | def apply(value: String): NumberWithCompanionObject = NumberWithCompanionObject(value = value.toInt) 7 | 8 | def random: NumberWithCompanionObject = NumberWithCompanionObject(value = Random.nextInt()) 9 | } 10 | 11 | final case class NumberWithCompanionObject(value: Int) { 12 | val plusOne: NumberWithCompanionObject = copy(value = value + 1) 13 | } 14 | -------------------------------------------------------------------------------- /src/main/tv/codely/scala_intro_examples/lesson_09_oop/StandardClass.scala: -------------------------------------------------------------------------------- 1 | package tv.codely.scala_intro_examples.lesson_09_oop 2 | 3 | final class StandardClass( 4 | val attributeInConstruct: String, 5 | private val privateAttributeInConstruct: String = "Some default value" 6 | ) { 7 | val attributeInBody = "public body attribute value" 8 | private val privateAttributeInBody = "private body attribute value" 9 | } 10 | -------------------------------------------------------------------------------- /src/main/tv/codely/scala_intro_examples/lesson_09_oop/exercise_solutions/joan_miralles_solution/Email.scala: -------------------------------------------------------------------------------- 1 | package tv.codely.scala_intro_examples.lesson_09_oop.exercise_solutions.joan_miralles_solution 2 | 3 | object Email { 4 | val validEmailTest = raw"^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$$" 5 | 6 | def apply(completeEmail: String): Email = { 7 | if (!completeEmail.matches(validEmailTest)) { 8 | throw new IllegalArgumentException(s"$completeEmail is not a valid e-mail") 9 | } 10 | Email(Local.fromEmail(completeEmail), Domain.fromEmail(completeEmail)) 11 | } 12 | } 13 | 14 | final case class Email(local: Local = Local("soporte"), domain: Domain = Domain("codely.tv")) 15 | 16 | object Local { 17 | def fromEmail(email: String): Local = Local(email.substring(0, email.indexOf("@"))) 18 | } 19 | 20 | final case class Local(value: String) 21 | 22 | object Domain { 23 | def fromEmail(email: String): Domain = { 24 | Domain(email.substring(email.indexOf("@") + 1)) 25 | } 26 | } 27 | 28 | final case class Domain(value: String) 29 | -------------------------------------------------------------------------------- /src/main/tv/codely/scala_intro_examples/lesson_10_enums_vs_sealed_structures/DomainError.scala: -------------------------------------------------------------------------------- 1 | package tv.codely.scala_intro_examples.lesson_10_enums_vs_sealed_structures 2 | 3 | import java.util.UUID 4 | 5 | object DomainError { 6 | def unapply(arg: DomainError): Option[(Int, String, Map[String, Any])] = 7 | Some((arg.priority, arg.mnemonic, arg.context)) 8 | } 9 | 10 | sealed abstract class DomainError( 11 | val priority: Int, 12 | val mnemonic: String, 13 | val context: Map[String, Any] = Map.empty 14 | ) extends Ordered[DomainError] { 15 | override def compare(that: DomainError): Int = this.priority - that.priority 16 | } 17 | 18 | case object TooMuchCpuLoad extends DomainError(priority = 0, mnemonic = "too_much_cpu_load") 19 | 20 | case object NotEnoughDiskSpace extends DomainError(priority = 1, mnemonic = "not_enough_disk_space") 21 | 22 | final case class UserWithoutPermissions(userId: UUID, action: String) extends DomainError( 23 | priority = 100, 24 | mnemonic = "user_without_permissions", 25 | context = Map("user_id" -> userId.toString, "action" -> action) 26 | ) 27 | -------------------------------------------------------------------------------- /src/main/tv/codely/scala_intro_examples/lesson_10_enums_vs_sealed_structures/RafaState.scala: -------------------------------------------------------------------------------- 1 | package tv.codely.scala_intro_examples.lesson_10_enums_vs_sealed_structures 2 | 3 | sealed trait RafaState 4 | 5 | case object RafaWithTooMuchHair extends RafaState 6 | case object RafaWithNotTooMuchHair extends RafaState 7 | case object RafaSad extends RafaState 8 | -------------------------------------------------------------------------------- /src/main/tv/codely/scala_intro_examples/lesson_10_enums_vs_sealed_structures/UsageExamples.sc: -------------------------------------------------------------------------------- 1 | /** ****************** 2 | * Simple Enum usage 3 | * *******************/ 4 | 5 | import java.util.UUID 6 | 7 | import tv.codely.scala_intro_examples.lesson_10_enums_vs_sealed_structures.VideoType 8 | 9 | // Direct instantiation 10 | val screencast = VideoType.Screencast 11 | 12 | // Instantiate from DB stored value 13 | val fromDbValue = VideoType.withName("Screencast") 14 | 15 | // Instantiate from DB stored id 16 | val fromDbId = VideoType(1) 17 | 18 | // Pass it to other methods 19 | case class Video( 20 | id: String, 21 | videoType: VideoType.Value 22 | ) 23 | 24 | // Behaviour in the type 25 | VideoType.canBeLong(fromDbValue) 26 | 27 | // Get all possible values 28 | VideoType.values foreach println 29 | 30 | /** ****************** 31 | * Complex Enum usage 32 | * *******************/ 33 | 34 | import tv.codely.scala_intro_examples.lesson_10_enums_vs_sealed_structures.WeekDay 35 | 36 | val monday = WeekDay.Monday 37 | 38 | monday.isWorkingDay 39 | 40 | // Import the Type Alias in order to avoid specifying the WeekDay.Value type as the expected type 41 | import tv.codely.scala_intro_examples.lesson_10_enums_vs_sealed_structures.WeekDay._ 42 | 43 | case class Week(days: Seq[WeekDay]) 44 | 45 | val spanishWeek = Week(WeekDay.values.toSeq) 46 | 47 | // Enum main contra: Type erasure. More info: https://issues.scala-lang.org/browse/SI-3815 48 | // How to overcome type erasure: https://medium.com/@sinisalouc/overcoming-type-erasure-in-scala-8f2422070d20 49 | 50 | /** ********************************** 51 | * Simple sealed structures example 52 | * *********************************/ 53 | 54 | import tv.codely.scala_intro_examples.lesson_10_enums_vs_sealed_structures.{RafaSad, RafaState, RafaWithNotTooMuchHair, RafaWithTooMuchHair} 55 | 56 | // Try removing one of the cases! That's the power of sealed classes and traits (structures) 57 | def isEvenPossible(state: RafaState): Boolean = state match { 58 | case RafaWithTooMuchHair => true 59 | case RafaWithNotTooMuchHair => true 60 | case RafaSad => false 61 | } 62 | 63 | /** ********************************** 64 | * Complex sealed structures example 65 | * *********************************/ 66 | 67 | import tv.codely.scala_intro_examples.lesson_10_enums_vs_sealed_structures.{DomainError, NotEnoughDiskSpace, TooMuchCpuLoad, UserWithoutPermissions} 68 | 69 | val someErrors = Seq[DomainError]( 70 | NotEnoughDiskSpace, 71 | UserWithoutPermissions(UUID.randomUUID(), "haircut"), 72 | TooMuchCpuLoad 73 | ) 74 | 75 | someErrors.sorted 76 | 77 | someErrors.map { 78 | case DomainError(priority, _, _) if priority < 100 => 79 | "Non critical" 80 | case other: DomainError => 81 | s"/!\\ Critical /!\\: ${other.priority}" 82 | } 83 | -------------------------------------------------------------------------------- /src/main/tv/codely/scala_intro_examples/lesson_10_enums_vs_sealed_structures/VideoType.scala: -------------------------------------------------------------------------------- 1 | package tv.codely.scala_intro_examples.lesson_10_enums_vs_sealed_structures 2 | 3 | object VideoType extends Enumeration { 4 | val Screencast, Interview = Value 5 | 6 | def canBeLong(videoType: VideoType.Value): Boolean = videoType != Screencast 7 | } 8 | -------------------------------------------------------------------------------- /src/main/tv/codely/scala_intro_examples/lesson_10_enums_vs_sealed_structures/WeekDay.scala: -------------------------------------------------------------------------------- 1 | package tv.codely.scala_intro_examples.lesson_10_enums_vs_sealed_structures 2 | 3 | object WeekDay extends Enumeration { 4 | type WeekDay = Value // Type alias in order to avoid having to point to WeekDay.Value 5 | 6 | protected case class Val(order: Int, isWorkingDay: Boolean) extends super.Val 7 | 8 | val Monday = Val(order = 1, isWorkingDay = true) 9 | val Tuesday = Val(order = 2, isWorkingDay = true) 10 | val Wednesday = Val(order = 3, isWorkingDay = true) 11 | val Thursday = Val(order = 4, isWorkingDay = true) 12 | val Friday = Val(order = 5, isWorkingDay = true) 13 | val Saturday = Val(order = 6, isWorkingDay = false) 14 | val Sunday = Val(order = 7, isWorkingDay = false) 15 | } 16 | -------------------------------------------------------------------------------- /src/main/tv/codely/scala_intro_examples/lesson_10_enums_vs_sealed_structures/joan_miralles/IngestibleEnumType.scala: -------------------------------------------------------------------------------- 1 | package tv.codely.scala_intro_examples.lesson_10_enums_vs_sealed_structures.joan_miralles 2 | 3 | object IngestibleEnumType extends Enumeration { 4 | type IngestibleType = Value 5 | 6 | protected case class Val(minCalories: Int, maxCalories: Int, minimumContundence: Int) extends super.Val 7 | 8 | val Furralleros = Val(minCalories = 5000, maxCalories = 10000, minimumContundence = 90) 9 | val NotSoFurralleros = Val(minCalories = 1000, maxCalories = 5000, minimumContundence = 50) 10 | val JustInCase = Val(minCalories = 0, maxCalories = 1000, minimumContundence = 0) 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/main/tv/codely/scala_intro_examples/lesson_10_enums_vs_sealed_structures/joan_miralles/IngestibleSealedType.scala: -------------------------------------------------------------------------------- 1 | package tv.codely.scala_intro_examples.lesson_10_enums_vs_sealed_structures.joan_miralles 2 | 3 | object IngestibleSealedType { 4 | def unapply(arg: IngestibleSealedType): Option[(Int, Int, Int)] = Some((arg.minCalories, arg.maxCalories, arg.minimumContundence)) 5 | } 6 | 7 | sealed abstract class IngestibleSealedType(val minCalories: Int, val maxCalories: Int, val minimumContundence: Int) extends Ordered[IngestibleSealedType] { 8 | override def compare(that: IngestibleSealedType): Int = this.minimumContundence - that.minimumContundence 9 | } 10 | 11 | case object Furralleros extends IngestibleSealedType(minCalories = 5000, maxCalories = 10000, minimumContundence = 90) 12 | 13 | case object NotSoFurralleros extends IngestibleSealedType(minCalories = 1000, maxCalories = 5000, minimumContundence = 50) 14 | 15 | case object JustInCase extends IngestibleSealedType(minCalories = 0, maxCalories = 1000, minimumContundence = 0) 16 | -------------------------------------------------------------------------------- /src/main/tv/codely/scala_intro_examples/lesson_10_enums_vs_sealed_structures/joan_miralles/UsageExamples.sc: -------------------------------------------------------------------------------- 1 | import tv.codely.scala_intro_examples.lesson_10_enums_vs_sealed_structures.joan_miralles.{Furralleros, IngestibleEnumType, JustInCase, NotSoFurralleros} 2 | 3 | // Direct instantiation 4 | val furralleros = IngestibleEnumType.Furralleros 5 | 6 | // Instantiate from DB stored value 7 | val fromDbValue = IngestibleEnumType.withName("NotSoFurralleros") 8 | 9 | // Instantiate from DB stored id 10 | val fromDbId = IngestibleEnumType(2) 11 | 12 | // Get all possible values 13 | IngestibleEnumType.values foreach println 14 | 15 | /** ********************************** 16 | * Simple sealed structures example 17 | * *********************************/ 18 | 19 | import tv.codely.scala_intro_examples.lesson_10_enums_vs_sealed_structures.joan_miralles._ 20 | 21 | // Try removing one of the cases! That's the power of sealed classes and traits (structures) 22 | def isHealthyFood(ingestible: IngestibleSealedType): Boolean = ingestible match { 23 | case Furralleros => false 24 | case NotSoFurralleros => false 25 | case JustInCase => true 26 | } 27 | 28 | isHealthyFood(JustInCase) 29 | 30 | val thingsToIngest = Seq[IngestibleSealedType](NotSoFurralleros, JustInCase, Furralleros) 31 | 32 | thingsToIngest.sorted 33 | 34 | thingsToIngest.map { 35 | case IngestibleSealedType(_, maxCalories, _) if maxCalories >= 5000 => "Unhealthy!!! Don't eat them!" 36 | case other: IngestibleSealedType => "Healthy! You can eat a lot of them!" 37 | } 38 | -------------------------------------------------------------------------------- /src/main/tv/codely/scala_intro_examples/lesson_11_futures/1_BasicFuturesExamples.sc: -------------------------------------------------------------------------------- 1 | import java.util.UUID 2 | 3 | import scala.concurrent.{Await, Future} 4 | import scala.concurrent.ExecutionContext.Implicits.global 5 | import scala.concurrent.duration._ 6 | import tv.codely.scala_intro_examples.lesson_11_futures.Benchmarker._ 7 | 8 | /** ********************************** 9 | * 🚀 Basic async ops 10 | * *********************************/ 11 | 12 | def cutRafaHair(): Unit = simulate("💂‍️ Cut Rafa's hair", 30 seconds) 13 | 14 | def cutJaviHair(): Unit = simulate("🧔 Cut Javi's hair", 5 seconds) 15 | 16 | def cutRafaHairAsync(): Future[Unit] = Future(cutRafaHair()) 17 | 18 | def cutJaviHairAsync(): Future[Unit] = Future(cutJaviHair()) 19 | 20 | // Blocks the current thread 21 | 22 | cutRafaHair() 23 | cutJaviHair() 24 | 25 | print("👋 After sync calls 🕋") 26 | 27 | // Executes each asynchronous operation in a different thread 28 | 29 | val rafaFuture = cutRafaHairAsync() 30 | val javiFuture = cutJaviHairAsync() 31 | 32 | print("👋 After async calls 🔮") 33 | 34 | // Waits until the future completes 35 | 36 | Await.result(rafaFuture, 5 minutes) 37 | Await.result(javiFuture, 5 minutes) 38 | 39 | print("👋 After async calls ✋") 40 | 41 | // Checkout jvisualvm with longer execution times 42 | 43 | /** ********************************** 44 | * 🛠 Useful named constructors 45 | * *********************************/ 46 | 47 | final case class User(id: UUID, name: String) 48 | val randomUser = User(UUID.randomUUID(), "lerele") 49 | 50 | // We're just instantiating a Future[User] in order to match with a signature. 51 | // We're not executing anything asynchronously. Actually, we're avoiding context switching :) 52 | // Useful if we're coupled to the Future type in our repositories interfaces, but we've an in memory implementation. 53 | Future.successful(randomUser) // Future[User] 54 | Future.failed[User](new RuntimeException("Not found in cache")) // Future[User] 55 | //Future(throw new RuntimeException("222Not found in cache")) // Future[User] 56 | 57 | Future.unit // Future[Unit] 58 | -------------------------------------------------------------------------------- /src/main/tv/codely/scala_intro_examples/lesson_11_futures/2_ComposeFuturesExamples.sc: -------------------------------------------------------------------------------- 1 | import tv.codely.scala_intro_examples.lesson_11_futures._ 2 | 3 | import scala.concurrent.ExecutionContext.Implicits.global 4 | import scala.concurrent.Future 5 | 6 | /** ********************************** 7 | * 🔗 Compose futures 8 | * *********************************/ 9 | 10 | // Executing futures sequentially 11 | val fridge = new InMemoryFridge 12 | 13 | // map & flatMap 14 | val sandwichFutureMap = fridge.takeBread().flatMap { breadOption => 15 | fridge.takeCheese().map { cheeseOption => 16 | Sandwich(Seq(breadOption, cheeseOption)) 17 | } 18 | } 19 | 20 | // for syntactic sugar to the nesting hell rescue! 21 | val sandwichFutureFor = for { 22 | breadOption <- fridge.takeBread() 23 | cheeseOption <- fridge.takeCheese() 24 | } yield Sandwich(Seq(breadOption, cheeseOption)) 25 | 26 | // After executing it, try to replace the used fridge by one which delays the takeBread action! 27 | //val fridge = new InDelayedMemoryFridge 28 | 29 | val intFutureList = List.fill(100)(Future.successful(1)) 30 | 31 | // Sequence 32 | val intListFuture = Future.sequence(intFutureList) 33 | 34 | // Traverse 35 | val doubledIntListFuture = Future.traverse(intFutureList)(intFuture => intFuture.map(_ + 1)) 36 | 37 | // FoldLeft 38 | val sumFuture = Future.reduceLeft(intFutureList)(_ + _) 39 | -------------------------------------------------------------------------------- /src/main/tv/codely/scala_intro_examples/lesson_11_futures/3_FuturesErrorHandlingExamples.sc: -------------------------------------------------------------------------------- 1 | import scala.concurrent.Future 2 | import scala.concurrent.ExecutionContext.Implicits.global 3 | import scala.util.Try 4 | import scala.util.control.NonFatal 5 | 6 | import tv.codely.scala_intro_examples.lesson_11_futures._ 7 | import tv.codely.scala_intro_examples.lesson_11_futures.Benchmarker._ 8 | 9 | /** ********************************** 10 | * 🚨 Error Handling 11 | * *********************************/ 12 | 13 | // fallbackTo 14 | 15 | def readFromCache = Future.failed(new RuntimeException("Not found in cache")) 16 | def readFromSourceOfTruth = Future.successful("🐒 value from source of truth") 17 | 18 | val videoTitle = readFromCache fallbackTo readFromSourceOfTruth 19 | 20 | // /!\ fallbackTo parameter not passed by name 21 | 22 | val fridge = new BrokenFridge 23 | 24 | // Play replacing the fridge by the standard one 25 | // val fridge = new InMemoryFridge 26 | 27 | val sandwichFuture = for { 28 | cheeseOption <- fridge.takeCheese() recover { case NonFatal(e) => print(e.getMessage); None } 29 | breadOption <- fridge.takeBread() recover { case NonFatal(e) => print(e.getMessage); None } 30 | } yield Sandwich(Seq(breadOption, cheeseOption)) 31 | 32 | sandwichFuture.failed.foreach { 33 | case NonFatal(e) => print(s"🚨 Exception <${e.getMessage}> while making a Sandwich.") 34 | } 35 | 36 | // Future as an asynchronous implementation of Try 37 | 38 | def readFromCacheTry: Try[String] = Try(throw new RuntimeException("Not found in cache")) 39 | def readFromSourceOfTruthTry: Try[String] = Try("lerele") 40 | 41 | val videoTitleTry = readFromCacheTry.failed.flatMap(_ => readFromSourceOfTruthTry) 42 | 43 | // Functional Error Handling: https://github.com/47deg/functional-error-handling/blob/master/deck/README.md 44 | // Prefer monadic structures instead of throwing exceptions: Option / Either / Try 45 | -------------------------------------------------------------------------------- /src/main/tv/codely/scala_intro_examples/lesson_11_futures/4_BlockingFuturesExamples.sc: -------------------------------------------------------------------------------- 1 | import scala.concurrent.duration._ 2 | 3 | import tv.codely.scala_intro_examples.lesson_11_futures.Benchmarker._ 4 | 5 | /** ********************************** 6 | * 🕋 Blocking & Execution Context 7 | * *********************************/ 8 | 9 | // Blocking operations 10 | import scala.concurrent.blocking 11 | 12 | blocking { 13 | simulate("Some blocking operation", 1 second) 14 | } 15 | 16 | // Useful when the threads are left idle waiting for a resource. More information: 17 | // https://www.cakesolutions.net/teamblogs/demystifying-the-blocking-construct-in-scala-futures 18 | // https://www.beyondthelines.net/computing/scala-future-and-execution-context/ 19 | 20 | -------------------------------------------------------------------------------- /src/main/tv/codely/scala_intro_examples/lesson_11_futures/Benchmarker.scala: -------------------------------------------------------------------------------- 1 | package tv.codely.scala_intro_examples.lesson_11_futures 2 | 3 | import org.joda.time.DateTime 4 | 5 | import scala.concurrent.duration.Duration 6 | 7 | object Benchmarker { 8 | def print(something: String): Unit = { 9 | val threadName = Thread.currentThread().getName 10 | val now = DateTime.now 11 | val minute = s"${now.minuteOfHour().get()}:${now.secondOfMinute().get()}" 12 | println(s"[$threadName] [$minute] $something") 13 | } 14 | 15 | def benchmark[T](taskName: String, task: => T): T = { 16 | print(s"🏁 Starting to $taskName") 17 | val result = task 18 | print(s"🔚 Finishing to $taskName") 19 | result 20 | } 21 | 22 | def simulate(taskName: String, during: Duration): Unit = benchmark(taskName, Thread.sleep(during.toMillis)) 23 | } 24 | -------------------------------------------------------------------------------- /src/main/tv/codely/scala_intro_examples/lesson_11_futures/BrokenFridge.scala: -------------------------------------------------------------------------------- 1 | package tv.codely.scala_intro_examples.lesson_11_futures 2 | 3 | import tv.codely.scala_intro_examples.lesson_05_ifs_for.Ingredients.{Bread, Cheese} 4 | import tv.codely.scala_intro_examples.lesson_11_futures.Benchmarker.benchmark 5 | 6 | import scala.concurrent.Future 7 | 8 | final class BrokenFridge extends Fridge { 9 | def takeBread(): Future[Option[Bread]] = benchmark( 10 | taskName = "🍞 Take the bread", 11 | task = Future.failed(new RuntimeException("Error while taking the bread from the fridge.")) 12 | ) 13 | 14 | def takeCheese(): Future[Option[Cheese]] = benchmark( 15 | taskName = "🧀 Take the cheese", 16 | task = Future.successful(Option(Cheese())) 17 | ) 18 | } 19 | -------------------------------------------------------------------------------- /src/main/tv/codely/scala_intro_examples/lesson_11_futures/Fridge.scala: -------------------------------------------------------------------------------- 1 | package tv.codely.scala_intro_examples.lesson_11_futures 2 | 3 | import tv.codely.scala_intro_examples.lesson_05_ifs_for.Ingredients.{Bread, Cheese} 4 | 5 | import scala.concurrent.Future 6 | 7 | trait Fridge { 8 | def takeBread(): Future[Option[Bread]] 9 | 10 | def takeCheese(): Future[Option[Cheese]] 11 | } 12 | -------------------------------------------------------------------------------- /src/main/tv/codely/scala_intro_examples/lesson_11_futures/InDelayedMemoryFridge.scala: -------------------------------------------------------------------------------- 1 | package tv.codely.scala_intro_examples.lesson_11_futures 2 | 3 | import tv.codely.scala_intro_examples.lesson_05_ifs_for.Ingredients.{Bread, Cheese} 4 | import tv.codely.scala_intro_examples.lesson_11_futures.Benchmarker.benchmark 5 | 6 | import scala.concurrent.{ExecutionContext, Future} 7 | import scala.concurrent.duration._ 8 | 9 | final class InDelayedMemoryFridge(implicit ec: ExecutionContext) extends Fridge { 10 | 11 | def takeBread(): Future[Option[Bread]] = benchmark( 12 | taskName = "🍞 Take the bread", 13 | task = Future{ 14 | delay() 15 | Option(Bread()) 16 | } 17 | ) 18 | 19 | def takeCheese(): Future[Option[Cheese]] = benchmark("🧀 Take the cheese", Future.successful(Option(Cheese()))) 20 | 21 | private def delay(): Unit = Thread.sleep(5.seconds.toMillis) 22 | } 23 | -------------------------------------------------------------------------------- /src/main/tv/codely/scala_intro_examples/lesson_11_futures/InMemoryFridge.scala: -------------------------------------------------------------------------------- 1 | package tv.codely.scala_intro_examples.lesson_11_futures 2 | 3 | import tv.codely.scala_intro_examples.lesson_05_ifs_for.Ingredients.{Bread, Cheese} 4 | import tv.codely.scala_intro_examples.lesson_11_futures.Benchmarker.benchmark 5 | 6 | import scala.concurrent.Future 7 | 8 | final class InMemoryFridge extends Fridge { 9 | def takeBread(): Future[Option[Bread]] = benchmark("🍞 Take the bread", Future.successful(Option(Bread()))) 10 | 11 | def takeCheese(): Future[Option[Cheese]] = benchmark("🧀 Take the cheese", Future.successful(Option(Cheese()))) 12 | } 13 | -------------------------------------------------------------------------------- /src/main/tv/codely/scala_intro_examples/lesson_11_futures/Sandwich.scala: -------------------------------------------------------------------------------- 1 | package tv.codely.scala_intro_examples.lesson_11_futures 2 | 3 | import tv.codely.scala_intro_examples.lesson_05_ifs_for.Ingredient 4 | import tv.codely.scala_intro_examples.lesson_05_ifs_for.Ingredients.Bread 5 | 6 | import scala.reflect.ClassTag 7 | 8 | object Sandwich { 9 | def apply[T: ClassTag](allIngredients: Seq[Option[Ingredient]]): Sandwich = { 10 | val availableIngredients = allIngredients.flatten 11 | 12 | Sandwich(availableIngredients) 13 | } 14 | } 15 | 16 | final case class Sandwich(ingredients: Seq[Ingredient]) { 17 | require(ingredients.exists(isBread), "👮‍🍞 Bread is mandatory to make a Sandwich") 18 | 19 | private def isBread(ingredient: Ingredient) = ingredient.getClass == Bread.getClass 20 | } 21 | -------------------------------------------------------------------------------- /src/test/tv/codely/scala_intro_examples/lesson_08_collections/WordAggregatorSpec.scala: -------------------------------------------------------------------------------- 1 | package tv.codely.scala_intro_examples.lesson_08_collections 2 | 3 | import org.scalatest.{Matchers, WordSpec} 4 | 5 | final class WordAggregatorSpec extends WordSpec with Matchers { 6 | "Word Aggregator" should { 7 | "return an empty map given an empty text" in { 8 | val aggregator = new WordAggregator 9 | val emptyText = "" 10 | 11 | aggregator.aggregateWords(emptyText) shouldBe Map.empty 12 | } 13 | // @ToDo: Here you have your exercise! 14 | // Change this "ignore" keyword for the "in" one and make this test pass! 15 | "return the number of words occurrences for each word given a non empty text" ignore { 16 | val aggregator = new WordAggregator 17 | val nonEmptyText = "Hi hi hi all" 18 | val wordsOccurrences = Map( 19 | "hi" -> 3, 20 | "all" -> 1 21 | ) 22 | 23 | aggregator.aggregateWords(nonEmptyText) shouldBe wordsOccurrences 24 | 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/test/tv/codely/scala_intro_examples/lesson_08_collections/WordCounterSpec.scala: -------------------------------------------------------------------------------- 1 | package tv.codely.scala_intro_examples.lesson_08_collections 2 | 3 | import org.scalatest.{Matchers, WordSpec} 4 | 5 | final class WordCounterSpec extends WordSpec with Matchers { 6 | "Word Counter" should { 7 | "return 0 given an empty text" in { 8 | val counter = new WordCounter 9 | val emptyText = "" 10 | 11 | counter.countWords(emptyText) shouldBe 0 12 | } 13 | // @ToDo: Here you have your exercise! 14 | // Change this "ignore" keyword for the "in" one and make this test pass! 15 | "return the number of words given a non empty text" ignore { 16 | val counter = new WordCounter 17 | val nonEmptyText = "Hi all" 18 | val wordsNumber = 2 19 | 20 | counter.countWords(nonEmptyText) shouldBe wordsNumber 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/test/tv/codely/scala_intro_examples/lesson_08_collections/exercise_solutions/joan_miralles/WordAggregatorSpec.scala: -------------------------------------------------------------------------------- 1 | package tv.codely.scala_intro_examples.lesson_08_collections.exercise_solutions.joan_miralles 2 | 3 | import org.scalatest.{Matchers, WordSpec} 4 | 5 | final class WordAggregatorSpec extends WordSpec with Matchers { 6 | "Word Aggregator" should { 7 | "return an empty map given an empty text" in { 8 | val aggregator = new WordAggregator 9 | val emptyText = "" 10 | 11 | aggregator.aggregateWords(emptyText) shouldBe Map.empty 12 | } 13 | "return the number of words occurrences for each word given a non empty text" in { 14 | val aggregator = new WordAggregator 15 | val nonEmptyText = "Hi hi hi all" 16 | val wordsOccurrences = Map( 17 | "hi" -> 3, 18 | "all" -> 1 19 | ) 20 | 21 | aggregator.aggregateWords(nonEmptyText) shouldBe wordsOccurrences 22 | 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/test/tv/codely/scala_intro_examples/lesson_08_collections/exercise_solutions/joan_miralles/WordCounterSpec.scala: -------------------------------------------------------------------------------- 1 | package tv.codely.scala_intro_examples.lesson_08_collections.exercise_solutions.joan_miralles 2 | 3 | import org.scalatest.{Matchers, WordSpec} 4 | 5 | final class WordCounterSpec extends WordSpec with Matchers { 6 | "Word Counter" should { 7 | "return 0 given an empty text" in { 8 | val counter = new WordCounter 9 | val emptyText = "" 10 | 11 | counter.countWords(emptyText) shouldBe 0 12 | } 13 | "return the number of words given a non empty text" in { 14 | val counter = new WordCounter 15 | val nonEmptyText = "Hi all" 16 | val wordsNumber = 2 17 | 18 | counter.countWords(nonEmptyText) shouldBe wordsNumber 19 | } 20 | "return the number of words given a non empty text with spaces" in { 21 | val counter = new WordCounter 22 | val nonEmptyText = " Hi all " 23 | val wordsNumber = 2 24 | 25 | counter.countWords(nonEmptyText) shouldBe wordsNumber 26 | } 27 | "return the number of words given a non empty text with punctuation" in { 28 | val counter = new WordCounter 29 | val nonEmptyText = " Hi all. My name is John Snow. " 30 | val wordsNumber = 7 31 | 32 | counter.countWords(nonEmptyText) shouldBe wordsNumber 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/test/tv/codely/scala_intro_examples/lesson_09_oop/CaseClassCapabilitiesSpec.scala: -------------------------------------------------------------------------------- 1 | package tv.codely.scala_intro_examples.lesson_09_oop 2 | 3 | import org.scalatest.{Matchers, WordSpec} 4 | 5 | /** 6 | * In order to check all the capabilities that a case class have, just: 7 | * * Compile it with: `scalac` 8 | * * Inspect it with: `javap -private` 9 | * 10 | * You also have the option of running `scalac CaseClass.scala -print` in order to see the compiled version. 11 | */ 12 | final class CaseClassCapabilitiesSpec extends WordSpec with Matchers { 13 | private val randomText = "some text" 14 | private val caseClass = CaseClass(attributeInConstruct = randomText) 15 | 16 | "Case Class" should { 17 | "provide an apply method in the companion object in order to construct new instances" in { 18 | "CaseClass(attributeInConstruct = randomText)" should compile 19 | } 20 | "provide a copy method making it easier for us to deal with immutability" in { 21 | val differentInstance = caseClass.copy(attributeInConstruct = "some different text") 22 | 23 | differentInstance.attributeInBody shouldBe differentInstance.attributeInBody 24 | } 25 | "provide an unapply method making it easier deconstructing in pattern matching" in { 26 | val differentInstance = CaseClass.unapply(caseClass) 27 | 28 | differentInstance shouldBe a[Option[_]] 29 | } 30 | "provide an implemented toString method displaying all the attribtue values" in { 31 | caseClass.toString shouldBe "CaseClass(some text,Some default value)" 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/test/tv/codely/scala_intro_examples/lesson_09_oop/CaseClassVisibilitiesSpec.scala: -------------------------------------------------------------------------------- 1 | package tv.codely.scala_intro_examples.lesson_09_oop 2 | 3 | import org.scalatest.{Matchers, WordSpec} 4 | 5 | final class CaseClassVisibilitiesSpec extends WordSpec with Matchers { 6 | private val randomText = "some text" 7 | private val caseClass = CaseClass(attributeInConstruct = randomText) 8 | 9 | "Case Class" should { 10 | "generate a public getter for the attributes defined in the constructor" in { 11 | caseClass.attributeInConstruct shouldBe randomText 12 | } 13 | "not compile if we try to access private attributes defined in the constructor" in { 14 | "caseClass.privateAttributeInConstruct" shouldNot compile 15 | } 16 | "set as public the attributes defined in the class body by default" in { 17 | val bodyAttributeValue = "public body attribute value" 18 | 19 | caseClass.attributeInBody shouldBe bodyAttributeValue 20 | } 21 | "not compile if we try to access private attributes defined in the body" in { 22 | "caseClass.privateAttributeInBody" shouldNot compile 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/test/tv/codely/scala_intro_examples/lesson_09_oop/StandardClassVisibilitiesSpec.scala: -------------------------------------------------------------------------------- 1 | package tv.codely.scala_intro_examples.lesson_09_oop 2 | 3 | import org.scalatest.{Matchers, WordSpec} 4 | 5 | final class StandardClassVisibilitiesSpec extends WordSpec with Matchers { 6 | private val randomText = "some text" 7 | private val standardClass = new StandardClass(attributeInConstruct = randomText) 8 | 9 | "Standard Class" should { 10 | "set as public the attributes defined in the constructor by default" in { 11 | standardClass.attributeInConstruct shouldBe randomText 12 | } 13 | "not compile if we try to access private attributes defined in the constructor" in { 14 | "standardClass.privateAttributeInConstruct" shouldNot compile 15 | } 16 | "set as public the attributes defined in the class body by default" in { 17 | val bodyAttributeValue = "public body attribute value" 18 | 19 | standardClass.attributeInBody shouldBe bodyAttributeValue 20 | } 21 | "not compile if we try to access private attributes defined in the body" in { 22 | "standardClass.privateAttributeInBody" shouldNot compile 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/test/tv/codely/scala_intro_examples/lesson_09_oop/exercise_solutions/joan_miralles_solution/EmailTest.scala: -------------------------------------------------------------------------------- 1 | package tv.codely.scala_intro_examples.lesson_09_oop.exercise_solutions.joan_miralles_solution 2 | 3 | import org.scalatest.{Matchers, WordSpec} 4 | 5 | class EmailTest extends WordSpec with Matchers { 6 | 7 | "Email Class" should { 8 | "provide an apply method in the companion object in order to construct new Email from complete string" in { 9 | Email(completeEmail = "some@email.com") 10 | } 11 | 12 | "return a new Email instance given a complete email" in { 13 | val validEmail = "some@email.com" 14 | val email = Email(validEmail) 15 | email.local.value shouldBe "some" 16 | email.domain.value shouldBe "email.com" 17 | } 18 | 19 | "throws an Exception given email address string which doesn't contain a valid TLD" in { 20 | assertThrows[IllegalArgumentException] { 21 | val invalidEmail = "a@e.t" 22 | Email(invalidEmail) 23 | } 24 | } 25 | 26 | "throws an Exception given email address string which doesn't contain any character before @ character" in { 27 | assertThrows[IllegalArgumentException] { 28 | val invalidEmail = "@email.com" 29 | Email(invalidEmail) 30 | } 31 | } 32 | 33 | "throws an Exception given email address string which doesn't contain @ character" in { 34 | assertThrows[IllegalArgumentException] { 35 | val invalidEmail = "some.email.com" 36 | Email(invalidEmail) 37 | } 38 | } 39 | 40 | "throws an Exception given email address string which doesn't contain . character" in { 41 | assertThrows[IllegalArgumentException] { 42 | val invalidEmail = "some@email" 43 | Email(invalidEmail) 44 | } 45 | } 46 | } 47 | } 48 | --------------------------------------------------------------------------------