Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
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