@volpegabriel87
@gvolpe
@volpegabriel87
@gvolpe
@volpegabriel87
@gvolpe
<< SUGAR2019 >>
@volpegabriel87
@gvolpe
def showName(username: String, name: String, email: String): String =
s"""
Hi $name! Your username is $username
and your email is $email.
"""
val program: IO[Unit] =
putStrLn(
showName("gvolpe@github.com", "12345", "foo")
)
@volpegabriel87
@gvolpe
@volpegabriel87
@gvolpe
case class UserNameV(value: String) extends AnyVal
case class NameV(value: String) extends AnyVal
case class EmailV(value: String) extends AnyVal
val program: IO[Unit] =
putStrLn(
showNameV(
UserNameV("gvolpe@github.com"),
NameV("12345"),
EmailV("foo")
)
)
def showNameV(username: UserNameV, name: NameV, email: EmailV): String =
s"""
Hi ${name.value}! Your username is ${username.value}
and your email is ${email.value}.
"""
@volpegabriel87
@gvolpe
def mkUsername(value: String): Option[UserNameV] =
if (value.nonEmpty) UserNameV(value).some else None
def mkName(value: String): Option[NameV] =
if (value.nonEmpty) NameV(value).some else None
// Let's pretend we validate it properly
def mkEmail(value: String): Option[EmailV] =
if (value.contains("@")) EmailV(value).some else None
val program: IO[Unit] =
for {
u <- mkUsername("gvolpe").liftTo[IO](EmptyError)
n <- mkName("George").liftTo[IO](EmptyError)
e <- mkEmail("123").liftTo[IO](InvalidEmail)
_ <- putStrLn(showNameV(u, n, e))
} yield ()
@volpegabriel87
@gvolpe
λ > root[ERROR] meetup.TypesDemo$InvalidEmail$
root ... finished with exit code 1
RUNTIME VALIDATION
@volpegabriel87
@gvolpe
val program: IO[Unit] =
(
mkUsername("gjl").liftTo[IO](EmptyError),
mkName("George").liftTo[IO](EmptyError),
mkEmail("gjl@foo.com").liftTo[IO](InvalidEmail)
).mapN {
case (u, n, e) =>
putStrLn(
showNameV(u.copy(value = ""), n, e)
)
}
Ouch!
@volpegabriel87
@gvolpe
sealed abstract case class UserNameP(value: String)
object UserNameP {
def make(value: String): Option[UserNameP] =
if (value.nonEmpty) new UserNameP(value) {}.some else None
}
// same for name
sealed abstract case class EmailP(value: String)
object EmailP {
def make(value: String): Option[EmailP] =
if (value.contains("@")) new EmailP(value) {}.some else None
}
val program: IO[Unit] =
(
UserNameP.make("jr"),
NameP.make("Joe Reef"),
EmailP.make("joe@bar.com")
).tupled.fold(IO.unit) {
case (u, n, e) => putStrLn(showNameP(u, n, e))
}
@volpegabriel87
@gvolpe
@newtype case class UserNameT(value: String)
@newtype case class NameT(value: String)
@newtype case class EmailT(value: String)
val program: IO[Unit] =
putStrLn(
showNameT(
UserNameT("gvolpe@github.com"),
NameT("12345"),
EmailT("")
)
)
def showNameT(username: UserNameT, name: NameT, email: EmailT): String =
s"""
Hi ${name.value}! Your username is ${username.value}
and your email is ${email.value}.
"""
@volpegabriel87
@gvolpe
def mkUsername(value: String): Option[UserNameT] =
if (value.nonEmpty) UserNameT(value).some else None
def mkName(value: String): Option[NameT] =
if (value.nonEmpty) NameT(value).some else None
def mkEmail(value: String): Option[EmailT] =
if (value.contains("@")) EmailT(value).some else None
@volpegabriel87
@gvolpe
val program: IO[Unit] =
(
mkUsername("gvolpe").liftTo[IO](EmptyError),
mkName("George").liftTo[IO](EmptyError),
mkEmail("123").liftTo[IO](InvalidEmail)
).mapN {
case (u, n, e) =>
putStrLn(
showNameT(u, n, e)
)
}
putStrLn(
showNameT(UserName(""), n, e)
)
@volpegabriel87
@gvolpe
import eu.timepit.refined._ // and more ...
type UserNameR = NonEmptyString
type NameR = NonEmptyString
type EmailR = String Refined Contains['@']
@volpegabriel87
@gvolpe
val program: IO[Unit] =
putStrLn(
showNameR("jr", "Joe", "123#com")
)
def showNameR(username: UserNameR, name: NameR, email: EmailR): String =
s"""
Hi ${name.value}! Your username is ${username.value}
and your email is ${email.value}.
"""
@volpegabriel87
@gvolpe
COMPILE TIME VALIDATION
[error] /foo/one.scala:77:30: Predicate (!(1 == @) && !(2 == @)) did not fail.
[error] showNameR("jr", "Joe", "123#com")
[error] ^
[error] one error found
[error] (Compile / compileIncremental) Compilation failed
[error] Total time: 2 s, completed Nov 29, 2019 5:12:33 PM
@volpegabriel87
@gvolpe
@volpegabriel87
@gvolpe
@newtype case class UserName(value: NonEmptyString)
@newtype case class Name(value: NonEmptyString)
@newtype case class Email(value: String Refined Contains['@'])
import eu.timepit.refined._ // and more ...
type UserNameR = NonEmptyString
type NameR = NonEmptyString
type EmailR = String Refined Contains['@']
@volpegabriel87
@gvolpe
def showNameTR(username: UserName, name: Name, email: Email): String =
s"""
Hi ${name.value.value}! Your username is ${username.value.value}
and your email is ${email.value.value}.
"""
val program: IO[Unit] =
putStrLn(
showNameTR(
UserName("jr"),
Name("John"),
Email("foo@bar.com")
)
)
@volpegabriel87
@gvolpe
val makeRef: IO[Ref[IO, Int]] =
Ref.of[IO, Int](0)
val program =
for {
r <- makeRef
_ <- r.update(_ + 10)
r <- makeRef
_ <- r.update(_ + 20)
n <- r.get
_ <- putStrLn(n)
} yield ()
PRINTS 20
@volpegabriel87
@gvolpe
def incrByOne(ref: Ref[IO, Int]): IO[Unit] =
putStrLn("Increasing counter by one") *>
ref.update(_ + 1)
def incrByTwo(ref: Ref[IO, Int]): IO[Unit] =
putStrLn("Increasing counter by two") *>
ref.update(_ + 2)
val program: IO[Unit] =
Ref.of[IO, Int](0).flatMap { ref =>
incrByOne(ref) >> incrByTwo(ref) >> ref.get.flatMap(putStrLn)
}
PRINTS 100
val program: IO[Unit] =
Ref.of[IO, Int](0).flatMap { ref =>
incrByOne(ref) >> incrByTwo(ref) >>
// We can access the Ref and alter its state which may be undesirable
ref.get.flatMap(n => if (n % 3 == 0) ref.set(100) else IO.unit) >>
ref.get.flatMap(putStrLn)
}
@volpegabriel87
@gvolpe
trait Counter[F[_]] {
def incr: F[Unit]
def get: F[Int]
}
object Counter {
def make[F[_]: Sync]: F[Counter[F]] =
Ref.of[F, Int](0).map { ref =>
new Counter[F] {
def incr: F[Unit] =
ref.update(_ + 1)
def get: F[Int] =
ref.get
}
}
}
@volpegabriel87
@gvolpe
val program: IO[Unit] =
Counter.make[IO].flatMap { c =>
c.incr >> c.get.flatMap(putStrLn)
}
def incrByTen(counter: Counter[IO]): IO[Unit] =
counter.incr.replicateA(10).void
val program: IO[Unit] =
Counter.make[IO].flatMap { c =>
// Sharing state only via the TF algebra
c.incr >> incrByTen(c) >> c.get.flatMap(putStrLn)
}
@volpegabriel87
@gvolpe