Last active
October 4, 2018 13:25
-
-
Save calvinlfer/437c4fcca1e009e317cf44fa5860bb5d to your computer and use it in GitHub Desktop.
Functional Scala: Toronto Edition
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
// Copyright(C) 2018 - John A. De Goes. All rights reserved. | |
package net.degoes.abstractions | |
import scalaz._ | |
import Scalaz._ | |
import scala.language.higherKinds | |
object algebra { | |
// | |
// EXERCISE 1 | |
// | |
// Define a semigroup for `NotEmpty` below. | |
// | |
case class NotEmpty[+A](head: A, tail: Option[NotEmpty[A]]) | |
implicit def NotEmptySemigroup[A]: Semigroup[NotEmpty[A]] = | |
new Semigroup[NotEmpty[A]] { | |
override def append(f1: NotEmpty[A], f2: => NotEmpty[A]): NotEmpty[A] = f1 match { | |
case NotEmpty(a, Some(at)) => NotEmpty(a, Some(append(at, f2))) | |
case NotEmpty(a, None) => NotEmpty(a, Some(f2)) | |
} | |
} | |
val example1 = NotEmpty(1, None) |+| NotEmpty(2, None) | |
// | |
// EXERCISE 2 | |
// | |
// Design a permission system for securing some resource, together with a | |
// monoid for the permission data structure. | |
// | |
case class Account(email: String) | |
case class Document(name: String) | |
case class Permission(ownership: Map[Account, List[Document]]) | |
implicit val MonoidPermission: Monoid[Permission] = new Monoid[Permission] { | |
override def zero: Permission = Permission(Map.empty[Account, List[Document]]) | |
override def append(f1: Permission, f2: => Permission): Permission = | |
// ScalaZ provides Monoids for maps | |
Permission(f1.ownership |+| f2.ownership) | |
} | |
val example2 = mzero[Permission] |+| Permission(Map(Account("calvin@gmail.com") -> List())) | |
implicit def OptionMonoid[A: Semigroup] = new Monoid[Option[A]] { | |
override def zero: Option[A] = None | |
override def append(f1: Option[A], f2: => Option[A]): Option[A] = | |
f1 orElse f2 | |
} | |
// | |
// EXERCISE 3 | |
// | |
// Define an instance of `Semigroup` for `(A, B)` when both `A` and | |
// `B` form semigroups. | |
// | |
implicit def SemigroupTuple2[A: Semigroup, B: Semigroup]: | |
Semigroup[(A, B)] = new Semigroup[(A, B)] { | |
override def append(f1: (A, B), f2: => (A, B)): (A, B) = { | |
val (f1A, f1B) = f1 | |
val (f2A, f2B) = f2 | |
(f1A |+| f2A, f1B |+| f2B) | |
} | |
} | |
val intStringA: (Int, String) = (10, "hello") | |
val intStringB: (Int, String) = (20, " world") | |
intStringA |+| intStringB | |
// | |
// EXERCISE 4 | |
// | |
// Try to define an instance of `Monoid` for `NotEmpty` for any type `A`. | |
// | |
implicit def MonoidNotEmpty[A: Monoid]: Monoid[NotEmpty[A]] = new Monoid[NotEmpty[A]] { | |
override def zero: NotEmpty[A] = NotEmpty(Monoid[A].zero, None) | |
override def append(f1: NotEmpty[A], f2: => NotEmpty[A]): NotEmpty[A] = { | |
val adjustedTail = | |
(f1.tail, f2.tail) match { | |
case (Some(a), Some(b)) => Some(a |+| b) | |
case (Some(a), None) => Some(a) | |
case (None, Some(b)) => Some(b) | |
case _ => None | |
} | |
NotEmpty(f1.head |+| f2.head, adjustedTail) | |
} | |
} | |
} | |
object functor { | |
val functorOption: Functor[Option] = new Functor[Option] { | |
override def map[A, B](fa: Option[A])(f: A => B): Option[B] = fa match { | |
case None => None | |
case Some(a) => Some(f(a)) | |
} | |
} | |
// | |
// EXERCISE 1 | |
// | |
// Define an instance of `Functor` for `BTree`. | |
// | |
sealed trait BTree[+A] | |
case class Leaf[A](a: A) extends BTree[A] | |
case class Fork[A](left: BTree[A], right: BTree[A]) extends BTree[A] | |
implicit val BTreeFunctor: Functor[BTree] = | |
new Functor[BTree] { | |
def map[A, B](fa: BTree[A])(f: A => B): BTree[B] = fa match { | |
case Leaf(a) => Leaf(f(a)) | |
case Fork(left, right) => Fork(map(left)(f), map(right)(f)) | |
} | |
} | |
// | |
// EXERCISE 2 | |
// | |
// Define an instance of `Functor` for `Nothing`. | |
implicit val NothingFunctor: Functor[Nothing] = new Functor[Nothing] { | |
override def map[A, B](fa: Nothing)(f: A => B): Nothing = fa | |
} | |
// Is there a business example that demonstrates the use of Functor? | |
// Yes :) | |
sealed trait BankTransaction[A] | |
case class Return[A](value: A) extends BankTransaction[A] | |
case class Deposit[A](amount: BigDecimal, next: BigDecimal => BankTransaction[A]) extends BankTransaction[A] | |
case class Withdraw[A](amount: BigDecimal, next: BigDecimal => BankTransaction[A]) extends BankTransaction[A] | |
object BankTransaction { | |
// FYI: John defines a Zip typeclass which is essentially Scalaz's Apply to demonstrate what it looks like | |
implicit val FunctorBankTransaction: Functor[BankTransaction] = new Functor[BankTransaction] /*with Zip[BankTransaction]*/ { | |
override def map[A, B](fa: BankTransaction[A])(f: A => B): BankTransaction[B] = fa match { | |
case Return(a) => Return(f(a)) | |
// note that syntax makes the following valid | |
case Deposit(amount, next) => Deposit(amount, (b: BigDecimal) => next(b).map(f)) | |
case Withdraw(amount, next) => Withdraw(amount, (b: BigDecimal) => next(b).map(f)) | |
} | |
// // advanced | |
// def flatten[C](v: BankTransaction[BankTransaction[C]]): BankTransaction[C] = v match { | |
// case Return(t) => t | |
// case Deposit(amount, next) => Deposit(amount, (b: BigDecimal) => flatten(next(b))) | |
// case Withdraw(amount, next) => Withdraw(amount, (b: BigDecimal) => flatten(next(b))) | |
// } | |
// | |
// // advanced | |
// def zip[A, B](l: BankTransaction[A], r: BankTransaction[B]): BankTransaction[(A, B)] = { | |
// val nested = l.map(a => r.map(b => (a, b))) | |
// flatten(nested) | |
// } | |
} | |
} | |
// | |
// EXERCISE 3 | |
// | |
// Define an instance of `Functor` for Parser[E, ?]. | |
// | |
case class Parser[+E, +A](run: String => Either[E, (String, A)]) | |
object Parser { | |
def fail[E](e: E): Parser[E, Nothing] = Parser(_ => Left(e)) | |
def point[A](a: => A): Parser[Nothing, A] = Parser(input => Right((input, a))) | |
def char[E](e: E): Parser[E, Char] = Parser(input => | |
if (input.isEmpty) Left(e) | |
// input.drop(1): the rest of the input, input.head: we have parsed successfully | |
else Right((input.drop(1), input.head)) | |
) | |
} | |
implicit def ParserFunctor[E]: Functor[Parser[E, ?]] = | |
new Functor[Parser[E, ?]] { | |
// need a Parser[E, B] which represents String => Either[E, (String, B)] | |
def map[A, B](fa: Parser[E, A])(f: A => B): Parser[E, B] = Parser(str => { | |
val res: Either[E, (String, A)] = fa.run(str) | |
res match { | |
case Left(e) => Left(e) | |
case Right((resString, a)) => Right((resString, f(a))) | |
} | |
}) | |
} | |
// Exercise 3.5 | |
// left parser then right parser | |
def zip[E, A, B](l: Parser[E, A], r: Parser[E, B]): Parser[E, (A, B)] = Parser(input => { | |
l.run(input) match { | |
case Left(e) => Left(e) | |
case Right((restInput, a)) => | |
r.run(restInput) match { | |
case Left(e2) => Left(e2) | |
case Right((restInput2, b)) => Right((restInput2, (a, b))) | |
} | |
} | |
}) | |
// | |
// EXERCISE 4 | |
// | |
// Try to define an instance of `Functor` for the following data type. | |
// Calvin: You cannot do this because you need a B => B so you need a B => A and A => B to get a B => B | |
case class DataType[A](f: A => A) | |
implicit val DataTypeFunctor: Functor[DataType] = | |
new Functor[DataType] { | |
def map[A, B](fa: DataType[A])(f: A => B): DataType[B] = ??? | |
} | |
// | |
// EXERCISE 5 | |
// | |
// Define an instance of `Functor` for `FunctorProduct`. | |
// | |
case class FunctorProduct[F[_], G[_], A](l: F[A], r: G[A]) | |
implicit def FunctorProductFunctor[F[_]: Functor, G[_]: Functor]: | |
Functor[FunctorProduct[F, G, ?]] = new Functor[FunctorProduct[F, G, ?]] { | |
override def map[A, B](fa: FunctorProduct[F, G, A])(f: A => B): FunctorProduct[F, G, B] = | |
FunctorProduct(fa.l.map(f), fa.r.map(f)) | |
} | |
// | |
// EXERCISE 6 | |
// | |
// Define an instance of `Functor` for `FunctorSum`. | |
// | |
case class FunctorSum[F[_], G[_], A](run: Either[F[A], G[A]]) | |
implicit def FunctorSumFunctor[F[_]: Functor, G[_]: Functor]: | |
Functor[FunctorSum[F, G, ?]] = new Functor[FunctorSum[F, G, ?]] { | |
override def map[A, B](fa: FunctorSum[F, G, A])(f: A => B): FunctorSum[F, G, B] = | |
FunctorSum[F, G, B](fa.run match { | |
case Left(ffa) => Left(ffa.map(f)) | |
case Right(ga) => Right(ga.map(f)) | |
}) | |
} | |
// | |
// EXERCISE 7 | |
// | |
// Define an instance of `Functor` for `FunctorNest`. | |
// | |
case class FunctorNest[F[_], G[_], A](run: F[G[A]]) | |
implicit def FunctorNestFunctor[F[_]: Functor, G[_]: Functor]: | |
Functor[FunctorNest[F, G, ?]] = new Functor[FunctorNest[F, G, ?]] { | |
override def map[A, B](fa: FunctorNest[F, G, A])(f: A => B): FunctorNest[F, G, B] = | |
FunctorNest(fa.run.map(_.map(f)): F[G[B]]) | |
} | |
def zipOption[A, B](l: Option[A], r: Option[B]): Option[(A, B)] = | |
(l, r) match { | |
case (Some(a), Some(b)) => Some((a, b)) | |
case _ => None | |
} | |
def zipWith[A, B, C](l: Option[A], r: Option[B])(f: ((A, B)) => C): Option[C] = | |
zipOption(l, r).map(f) | |
def zipList1[A, B](l: List[A], r: List[B]): List[(A, B)] = | |
(l, r) match { | |
case (a :: as, bs) => | |
zipList1(as, bs) ++ bs.map(b => (a, b)) | |
case (Nil, bs) => Nil | |
} | |
def zipList2[A, B](l: List[A], r: List[B]): List[(A, B)] = | |
(l, r) match { | |
case (a :: as, b :: bs) => ((a, b)) :: zipList2(as, bs) | |
case _ => Nil | |
} | |
// | |
// EXERCISE 8 | |
// | |
// Define `Applicative` for `Option`. | |
// | |
implicit val OptionApplicative: Applicative[Option] = | |
new Applicative[Option] { | |
def point[A](a: => A): Option[A] = Some(a) | |
def ap[A, B](fa: => Option[A])(f: => Option[A => B]): Option[B] = | |
fa match { | |
case None => None | |
case Some(a) => f.map(fn => fn(a)) | |
} | |
} | |
// | |
// EXERCISE 9 | |
// | |
// Implement `zip` in terms of the applicative composition using `|@|`. | |
// | |
// Bonus: Implement `ap2` in terms of `zip`. | |
// | |
val example1 = (Option(3) |@| Option(5))((_, _)) | |
val example2 = zip(Option(3), Option("foo")) : Option[(Int, String)] | |
def zip[F[_]: Applicative, A, B](l: F[A], r: F[B]): F[(A, B)] = | |
Applicative[F].apply2(l, r)((a, b) => Tuple2(a, b)) | |
def ap2[F[_]: Applicative, A, B](fa: F[A], fab: F[A => B]): F[B] = | |
Applicative[F].apply2(fa, fab)((a, ab) => ab(a)) | |
// | |
// EXERCISE 10 | |
// | |
// Define an instance of `Applicative` for `Parser[E, ?]`. | |
// | |
implicit def ApplicativeParser[E]: Applicative[Parser[E, ?]] = | |
new Applicative[Parser[E, ?]] { | |
def point[A](a: => A): Parser[E,A] = Parser(input => Right((input, a))) | |
def ap[A, B](fa: => Parser[E,A])(f: => Parser[E, A => B]): Parser[E,B] = Parser( (input: String) => { | |
val resA: Either[E, (String, A)] = fa.run(input) | |
resA match { | |
case Left(e) => | |
Left(e) | |
case Right((remainingInput, a)) => | |
val resF: Either[E, (String, A => B)] = f.run(remainingInput) | |
resF match { | |
case Left(e) => Left(e) | |
case Right((finalRemainingInput, aTob)) => Right((finalRemainingInput, aTob(a))) | |
} | |
} | |
} : Either[E, (String, B)]) | |
} | |
// Define an Applicative for List | |
implicit val ApplicativeList: Applicative[List] = new Applicative[List] { | |
override def point[A](a: => A): List[A] = List(a) | |
// kinda cheating because for comprehensions use flatMap and map | |
override def ap[A, B](fa: => List[A])(f: => List[A => B]): List[B] = | |
for { | |
a <- fa | |
aTob <- f | |
} yield aTob(a) | |
} | |
// | |
// EXERCISE 11 | |
// | |
// Define an instance of `Monad` for `BTree`. | |
// | |
implicit val MonadBTree: Monad[BTree] = | |
new Monad[BTree] { | |
def point[A](a: => A): BTree[A] = Leaf(a) | |
def bind[A, B](fa: BTree[A])(f: A => BTree[B]): BTree[B] = | |
fa match { | |
case Leaf(a) => | |
f(a) | |
case Fork(left: BTree[A], right: BTree[A]) => | |
Fork(left = bind(left)(f), right = bind(right)(f)) | |
} | |
} | |
// | |
// EXERCISE 12 | |
// | |
// Define an instance of `Monad` for `Parser[E, ?]`. | |
// | |
implicit def MonadParser[E]: Monad[Parser[E, ?]] = | |
new Monad[Parser[E, ?]] { | |
def point[A](a: => A): Parser[E,A] = | |
Parser(input => Right((input, a))) | |
def bind[A, B](fa: Parser[E,A])(f: A => Parser[E,B]): Parser[E,B] = | |
Parser(input => { | |
fa.run(input) match { | |
case Left(e) => | |
Left(e) | |
case Right((remainingInput, a)) => | |
f(a).run(remainingInput) | |
} | |
}) | |
} | |
// EXERCISE 12.5 | |
implicit val MonadOption: Monad[Option] = new Monad[Option] { | |
override def bind[A, B](fa: Option[A])(f: A => Option[B]): Option[B] = fa match { | |
case None => None | |
case Some(a) => f(a) | |
} | |
override def point[A](a: => A): Option[A] = Some(a) | |
} | |
} | |
// Monad graduation | |
object parser { | |
case class Parser[+E, +A](run: String => Either[E, (String, A)]) { self => | |
def ~[E1 >: E, B](that: Parser[E1, B]): Parser[E1, (A, B)] = | |
self.flatMap(a => that.map(b => (a, b))) | |
def ~>[E1 >: E, B](that: Parser[E1, B]): Parser[E1, B] = (self ~ that).map { case (_, b) => b } | |
def <~[E1 >: E, B](that: Parser[E1, B]): Parser[E1, A] = (self ~ that).map { case (a, _) => a } | |
def map[B](f: A => B): Parser[E, B] = flatMap(a => Parser.point(f(a))) | |
def flatMap[E1 >: E, B](f: A => Parser[E1, B]): Parser[E1, B] = Parser[E1, B](input => { | |
self.run(input) match { | |
case Left(e) => Left(e) | |
case Right((remInput, a)) => f(a).run(remInput) | |
} | |
}) | |
def orElse[E1 >: E, B](that: Parser[E1, B]): Parser[E1, Either[A, B]] = { | |
val self1 = self.map(Left(_)) | |
val that1 = that.map(Right(_)) | |
Parser[E1, Either[A, B]](input => | |
self1.run(input) match { | |
case Left(_) => that1.run(input) | |
case okay => okay | |
} | |
) | |
} | |
def filter[E1 >: E](e1: E1)(f: A => Boolean): Parser[E1, A] = Parser(input => | |
self.run(input) match { | |
case Left(err) => Left(err) | |
case Right((remInput, resA)) => | |
if (f(resA)) Right((remInput, resA)) | |
else Left(e1) | |
} | |
) | |
def | [E1 >: E, A1 >: A](that: Parser[E1, A1]): Parser[E1, A1] = | |
(self orElse that).map(_.merge) | |
def rep: Parser[E, List[A]] = | |
((self.map(List(_)) | Parser.point[List[A]](Nil)) ~ rep).map(t => t._1 ++ t._2) | |
} | |
object Parser { | |
def fail[E](e: E): Parser[E, Nothing] = | |
Parser(input => Left(e)) | |
def point[A](a: => A): Parser[Nothing, A] = | |
Parser(input => Right((input, a))) | |
def char[E](e: E): Parser[E, Char] = | |
Parser(input => | |
if (input.length == 0) Left(e) | |
else Right((input.drop(1), input.charAt(0)))) | |
def digit[E](e: E): Parser[E, Int] = | |
for { | |
c <- char(e) | |
option = scala.util.Try(c.toString.toInt).toOption | |
d <- option.fold[Parser[E, Int]](Parser.fail(e))(point(_)) | |
} yield d | |
def literal[E](f: Char => E)(c0: Char): Parser[E, Char] = | |
for { | |
c <- char(f(c0)) | |
_ <- if (c != c0) Parser.point(f(0)) | |
else Parser.point(()) | |
} yield c | |
def whitespace: Parser[Nothing, Unit] = | |
Parser(input => Right((input.dropWhile(_ == ' '), ()))) | |
} | |
// [1,2,3,] | |
sealed trait Error | |
case class ExpectedLit(char: Char) extends Error | |
case object ExpectedDigit extends Error | |
val parser: Parser[Error, List[Int]] = | |
for { | |
_ <- Parser.literal(ExpectedLit)('[') | |
digits <- (Parser.digit(ExpectedDigit) <~ Parser.literal(ExpectedLit)(',')).rep | |
_ <- Parser.literal(ExpectedLit)(']') | |
} yield digits | |
println( parser.run("[1, 2, 3,]")) | |
} | |
object johndegoesparser extends App { | |
// there was a bug in rep that is fixed by making arguments by name (insufficient laziness) | |
case class Parser[+E, +A](run: String => Either[E, (String, A)]) { self => | |
def ~ [E1 >: E, B](that: => Parser[E1, B]): Parser[E1, (A, B)] = | |
self.flatMap(a => that.map(b => (a, b))) | |
def ~> [E1 >: E, B](that: => Parser[E1, B]): Parser[E1, B] = | |
(self ~ that).map(_._2) | |
def <~ [E1 >: E, B](that: => Parser[E1, B]): Parser[E1, A] = | |
(self ~ that).map(_._1) | |
def map[B](f: A => B): Parser[E, B] = | |
flatMap(f.andThen(Parser.point[B](_))) | |
def flatMap[E1 >: E, B](f: A => Parser[E1, B]): Parser[E1, B] = | |
Parser[E1, B](input => | |
self.run(input) match { | |
case Left(e) => Left(e) | |
case Right((input, a)) => f(a).run(input) | |
}) | |
def orElse[E1 >: E, B](that: => Parser[E1, B]): Parser[E1, Either[A, B]] = { | |
val self1 = self.map(Left(_)) | |
val that1 = that.map(Right(_)) | |
type Return = Either[E1, (String, Either[A, B])] | |
Parser(i => self1.run(i).fold[Return](_ => that1.run(i), Right(_))) | |
} | |
def filter[E1 >: E](e0: E1)(f: A => Boolean): Parser[E1, A] = | |
Parser(input => | |
self.run(input) match { | |
case Left(e) => Left(e) | |
case Right((input, a)) => if (f(a)) Right((input, a)) else Left(e0) | |
}) | |
def | [E1 >: E, A1 >: A](that: => Parser[E1, A1]): Parser[E1, A1] = | |
(self orElse (that)).map(_.merge) | |
def rep: Parser[E, List[A]] = | |
((self.map(List(_)) | Parser.point[List[A]](Nil)) ~ rep).map(t => t._1 ++ t._2) | |
def ? : Parser[E, Option[A]] = self.map(Some(_)) | Parser.point(None) | |
} | |
object Parser { | |
def fail[E](e: E): Parser[E, Nothing] = | |
Parser(input => Left(e)) | |
def point[A](a: => A): Parser[Nothing, A] = | |
Parser(input => Right((input, a))) | |
def maybeChar: Parser[Nothing, Option[Char]] = | |
Parser(input => | |
if (input.length == 0) Right((input, None)) | |
else Right((input.drop(1), Some(input.charAt(0))))) | |
def char[E](e: E): Parser[E, Char] = | |
Parser(input => | |
if (input.length == 0) Left(e) | |
else Right((input.drop(1), input.charAt(0)))) | |
def digit[E](e: E): Parser[E, Int] = | |
for { | |
c <- char(e) | |
option = scala.util.Try(c.toString.toInt).toOption | |
d <- option.fold[Parser[E, Int]](Parser.fail(e))(point(_)) | |
} yield d | |
def literal[E](f: Char => E)(c0: Char): Parser[E, Char] = | |
for { | |
c <- char(f(c0)) | |
_ <- if (c != c0) Parser.point(f(0)) else Parser.point(()) | |
} yield c | |
def whitespace: Parser[Nothing, Unit] = | |
Parser(input => Right((input.dropWhile(_ == ' '), ()))) | |
} | |
// [1,2,3,] | |
sealed trait Error | |
case class ExpectedLit(char: Char) extends Error | |
case object ExpectedDigit extends Error | |
val parser: Parser[Error, List[Int]] = | |
for { | |
_ <- Parser.literal(ExpectedLit)('[') | |
digits <- (Parser.digit(ExpectedDigit) <~ Parser.literal(ExpectedLit)(',')).rep | |
_ <- Parser.literal(ExpectedLit)(']') | |
} yield digits | |
println(parser.run("[1, 2, 3,]")) | |
} | |
object foldable { | |
// val FoldableList: Foldable[List] = new Foldable[List] { | |
// final override def foldRight[A, B](fa: List[A], z: => B)(f: (A, B) => B): B = | |
// fa match { | |
// case Nil => z | |
// case a :: as => f(a, foldRight(as, z)(f)) | |
// } | |
// | |
// final override def foldMap[A, B](fa: List[A])(f: A => B)(implicit F: Monoid[B]): B = | |
// fa match { | |
// case Nil => F.zero | |
// // this can be done tail-recursively | |
// case head :: tail => f(head) |+| foldMap(tail)(f) | |
// } | |
// } | |
// | |
// EXERCISE 1 | |
// | |
// Define an instance of `Foldable` for `BTree`. | |
// | |
sealed trait BTree[+A] | |
case class Leaf[A](a: A) extends BTree[A] | |
case class Fork[A](left: BTree[A], right: BTree[A]) extends BTree[A] | |
implicit val FoldableBTree: Foldable[BTree] = | |
new Foldable[BTree] { | |
def foldMap[A, B](fa: BTree[A])(f: A => B)( | |
implicit F: Monoid[B]): B = | |
fa match { | |
case Leaf(a) => f(a) | |
case Fork(left, right) => foldMap(left)(f) |+| foldMap(right)(f) | |
} | |
def foldRight[A, B](fa: BTree[A], z: => B)(f: (A, => B) => B): B = | |
fa match { | |
case Leaf(a) => f(a, z) | |
case Fork(left, right) => | |
val newZero = foldRight(right, z)(f) | |
foldRight(left, newZero)(f) | |
} | |
} | |
// | |
// EXERCISE 2 | |
// | |
// Try to define an instance of `Foldable` for `A => ?`. | |
// Calvin: I don't think you can do this because you need to return a B => ? | |
// and for that, you need to do something funky like a B => A and A => B | |
implicit def FunctionFoldable[A]: Foldable[A => ?] = ??? | |
// | |
// EXERCISE 3 | |
// | |
// Define an instance of `Traverse` for `BTree`. | |
// | |
implicit val TraverseBTree: Traverse[BTree] = | |
new Traverse[BTree] { | |
def traverseImpl[G[_], A, B](fa: BTree[A])(f: A => G[B])(implicit F: Applicative[G]): G[BTree[B]] = fa match { | |
case Leaf(a) => | |
f(a).map(Leaf(_)): G[BTree[B]] | |
case Fork(left, right) => | |
(traverseImpl(left)(f) |@| traverseImpl(right)(f))((l: BTree[B], r: BTree[B]) => Fork(l, r)): G[BTree[B]] | |
} | |
} | |
// | |
// EXERCISE 4 | |
// | |
// Try to define an instance of `Traverse` for `Parser[E, ?]`. | |
// | |
case class Parser[+E, +A](run: String => Either[E, (String, A)]) | |
implicit def TraverseParser[E]: Traverse[Parser[E, ?]] = new Traverse[Parser[E, ?]] { | |
override def traverseImpl[G[_], A, B](fa: Parser[E, A])(f: A => G[B])(implicit AG: Applicative[G]): G[Parser[E, B]] = { | |
val p: Parser[E, G[B]] = Parser(input => fa.run(input) match { | |
case Left(e) => Left(e) | |
case Right((remInput, a)) => Right((remInput, f(a))) | |
}) | |
// you cannot get the G out, what's input? | |
// you cannot do this, Parsers are not traversable, it's not a data structure | |
??? | |
} | |
} | |
} | |
object optics { | |
sealed trait Country | |
object Country { | |
val usa: Prism[Country, Unit] = | |
Prism[Country, Unit]({ | |
case USA => Some(()) | |
case _ => None | |
}, _ => USA) | |
} | |
case object USA extends Country | |
case object UK extends Country | |
case object Poland extends Country | |
case class Org(name: String, address: Address, site: Site) | |
object Org { | |
val site: Lens[Org, Site] = | |
Lens[Org, Site](_.site, l => _.copy(site = l)) | |
} | |
case class Address( | |
number: String, | |
street: String, | |
postalCode: String, | |
country: Country) | |
case class Site( | |
manager: Employee, | |
address: Address, | |
employees: Set[Employee]) | |
object Site { | |
val manager: Lens[Site, Employee] = Lens[Site, Employee](_.manager, m => _.copy(manager = m)) | |
} | |
case class Employee( | |
name: String, | |
dob: java.time.Instant, | |
salary: BigDecimal, | |
address: Address) | |
object Employee { | |
val salary: Lens[Employee, BigDecimal] = | |
Lens[Employee, BigDecimal](_.salary, s => _.copy(salary = s)) | |
} | |
lazy val org: Org = ??? | |
lazy val org2 = | |
org.copy(site = | |
org.site.copy(manager = org.site.manager.copy( | |
salary = org.site.manager.salary * 0.95 | |
)) | |
) | |
// | |
// EXERCISE 1 | |
// | |
// Implement the `>>>` method of `Lens`. | |
// | |
final case class Lens[S, A]( | |
get: S => A, | |
set: A => (S => S) | |
) { self => | |
def >>> [B](that: Lens[A, B]): Lens[S, B] = { | |
val composedGet: S => B = self.get andThen that.get | |
val composedSet: B => (S => S) = (b: B) => (s: S) => set(that.set(b: B)(self.get(s): A))(s: S) | |
Lens(composedGet, composedSet) | |
} | |
final def update(f: A => A): S => S = | |
(s: S) => self.set(f(self.get(s)))(s) | |
} | |
// | |
// EXERCISE 2 | |
// | |
// Create a version of `org2` that uses lenses to update the salaries. | |
// | |
val org2_lens: Org = | |
(Org.site >>> Site.manager >>> Employee.salary).update(_ * 0.95)(org) | |
// | |
// EXERCISE 3 | |
// | |
// Implement `>>>` for `Prism`. | |
// | |
final case class Prism[S, A]( | |
get: S => Option[A], | |
set: A => S) { self => | |
def >>> [B](that: Prism[A, B]): Prism[S, B] = { | |
Prism( | |
get = s => self.get(s).flatMap(that.get), | |
set = that.set.andThen(self.set) | |
) | |
} | |
final def select(implicit ev: Unit =:= A): S = set(ev(())) | |
} | |
// | |
// EXERCISE 4 | |
// | |
// Implement `_Left` and `_Right`. | |
// | |
def _Left[A, B]: Prism[Either[A, B], A] = Prism[Either[A, B], A]( | |
get = a => a.fold(leftValue => Some(leftValue), rightValue => None), | |
set = a => Left(a) | |
) | |
def _Right[A, B]: Prism[Either[A, B], B] = Prism[Either[A, B], B]( | |
get = (eAB: Either[A, B]) => eAB.fold(leftValue => None, rightValue => Some(rightValue)), | |
set = (b: B) => Right(b) | |
) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Revisiting Lens
See https://julien-truffaut.github.io/Monocle/optics for more information