Created
September 12, 2022 11:35
-
-
Save gustavofranke/3a17bf3c08950d8611a4e35e7b0046aa to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package option | |
import org.scalatest.funsuite.AnyFunSuite | |
/** | |
* show some scala language features that, if you come from other languages, they might be unknown to you. | |
* | |
* Like last week, we'll play with the REPL, then turn our findings into tests. | |
* | |
* I'll use scala-test to automate this process, | |
* but this is not a talk about writing tests, | |
* so the tests themselves will be a little repetitive. | |
* | |
* I'll be using a very basic subset of the library, the library has lots of features. | |
* | |
* Language feature #1 we'll see today: Pattern matching | |
* Pattern matching could be used as kinda like a switch statement, although that's not how it's aimed to be used. | |
*/ | |
class PatternSuite extends AnyFunSuite { | |
test("match or not") { | |
val a = 5 match { // last week we had a look at "def"s, there another way to define things in scala ... "val"s | |
case 1 => "one"; | |
case 5 => "five" | |
} | |
assert(a === "five") | |
} | |
test("match boom!") { | |
assertThrows[scala.MatchError] { | |
5 match { | |
case 1 => "one"; | |
case 4 => "four" | |
} | |
} | |
assertThrows[scala.MatchError] { | |
"yes" match { | |
case "y" => 1; | |
case "n" => 0 | |
} | |
} | |
} | |
test("match boom! - there, I fixed it") { | |
val a = 2 match { | |
case 1 => "one"; | |
case 5 => "five"; | |
case _ => "none of them" | |
} | |
assert(a === "none of them") | |
} | |
} | |
/** | |
* last week, we discussed intuitions for the concepts of | |
* 1. type | |
* Now, we'll talk about: | |
* 2. product, like a tuple | |
* 3. co-product | |
* | |
* But in reality, | |
* pattern matching is aimed to traverse an entire ADT hierarchy | |
* | |
* Language feature #2 we'll see today: ADTs | |
*/ | |
class ADTSuite extends AnyFunSuite { | |
sealed trait Status | |
case object Active extends Status | |
case object Inactive extends Status | |
def toNumber(s: Status): Int = s match { | |
case Active => 1 | |
case Inactive => 2 | |
} | |
test("asdfdsa") { | |
val a: Status = Active | |
val b: Status = Inactive | |
assert(toNumber(a) === 1) | |
assert(toNumber(b) === 2) | |
} | |
} | |
/** | |
* Now, let's keep exploring a "type constructor" that we started having a look last week | |
* let's have a look at some of the methods or functions it provides | |
*/ | |
class OptionSuite extends AnyFunSuite { | |
test("basic get happy path") { | |
val a = Some(5) | |
assert(a.get === 5) | |
} | |
test("basic get unhappy path") { | |
assertDoesNotCompile( | |
""" | |
| val a: Option[Int] = None | |
|a.get === 5 | |
""".stripMargin) | |
} | |
test("happy path") { | |
val a = Some(5) | |
assert(a.map(x => x + 1) === Some(6)) // HOF | |
assert(a.map(_ + 1) === Some(6)) // HOF | |
} | |
test("unhappy path") { | |
val a: Option[Int] = None | |
assert(a.map(_ + 1) === None) | |
assert(None.flatMap((x:Int) => Some(x + 1)).map(x => x + 1) === None) | |
} | |
test("need to flatten") { | |
val a = Some(5) | |
assert(a.map(x => Some(x + 1)) === Some(Some(6))) | |
} | |
test("flatten! happy path") { | |
val a: Option[Int] = None | |
assert(a.flatMap(x => Some(x + 1)) === None) | |
} | |
test("flatten!") { | |
val a = Some(5) | |
assert(a.flatMap(x => Some(x + 1)) === Some(6)) | |
} | |
test("combine 2 somes with for comprehension") { | |
val a = for { | |
b <- Some(3) | |
c <- Some(2) | |
} yield b + c | |
assert(a === Some(5)) | |
} | |
test("combine 1 some and 1 none with for comprehension") { | |
val none: Option[Int] = None | |
val a = for { | |
b <- none | |
c <- Some(2) | |
} yield b + c | |
assert(a === None) | |
} | |
test("dissect a for comprehension with previously used values (2 somes)") { | |
val a = Some(3).flatMap(b => | |
Some(2).map(c => | |
b + c) | |
) | |
assert(a === Some(5)) | |
} | |
test("dissect a for comprehension with previously used values (1 some and 1 none)") { | |
val none: Option[Int] = None | |
val a = none.flatMap(b => | |
Some(2).map(c => | |
b + c) | |
) | |
assert(a === None) | |
} | |
} | |
/** | |
* Let's now create our own (bare minimum) option, and see if we can still get the previous tests passing | |
* | |
* language features in action: | |
* | |
* pattern matching | |
* generics | |
* variance | |
* Higher Order Functions | |
* co-product or sum type, ADT, GADT | |
* bottom type: the type that has no values | |
*/ | |
sealed trait Option[+A] { | |
def map[B](f: A => B): Option[B] = this match { | |
case None => None | |
case Some(s) => Some(f(s)) | |
} | |
def flatMap[B](f: A => Option[B]): Option[B] = map(f).getOrElse(None) | |
def getOrElse[B >: A](default: B): B = this match { | |
case None => default | |
case Some(s) => s | |
} | |
def filter(f: A => Boolean): Option[A] = flatMap(s => if (f(s)) Some(s) else None) | |
} | |
case object None extends Option[Nothing] | |
case class Some[+A](get: A) extends Option[A] |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment