Skip to content

Instantly share code, notes, and snippets.

@calvinlfer
Last active October 4, 2018 13:25
Show Gist options
  • Save calvinlfer/437c4fcca1e009e317cf44fa5860bb5d to your computer and use it in GitHub Desktop.
Save calvinlfer/437c4fcca1e009e317cf44fa5860bb5d to your computer and use it in GitHub Desktop.
Functional Scala: Toronto Edition
// 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)
)
}
@calvinlfer
Copy link
Author

Revisiting Lens

import scalaz._
import Scalaz._

final case class Lens[S, A](get: S => A, set: A => S => S)
object Lens {
  implicit class LensOps[S, A](lens: Lens[S, A]) {
    def >>>[B](other: Lens[A, B]): Lens[S, B] = {
      val get: S => B = { s: S =>
        val `S => A`: S => A = lens.get
        val `A => B`: A => B = other.get
        val `S => B`: S => B = `S => A` andThen `A => B`
        `S => B`(s)
      }

      val set: B => S => S = { b: B =>
        (s: S) => {
          val `A => A`: A => A = other.set(b)
          val a: A = lens.get(s)
          val modifiedA: A = `A => A`(a)

          val `S => S`: S => S = lens.set(modifiedA)
          val modifiedS: S = `S => S`(s)

          modifiedS
        }
      }

      Lens[S, B](get, set)
    }

    def modify(update: A => A): S => S = (s: S) => {
      val existingSubstructure: A = lens.get(s)
      val newSubstructure = update(existingSubstructure)
      lens.set(newSubstructure)(s)
    }
  }
}

case class Street(number: Int, name: String)
case class Address(city: String, street: Street)
case class Company(name: String, address: Address)
case class Employee(name: String, company: Company)

// we need to upper case the first character of his company street name
val employee = Employee("john", Company("awesome inc", Address("london", Street(23, "high street"))))

val companyLens: Lens[Employee, Company] =
  Lens(
    e => e.company,
    newCompany => employee => employee.copy(company = newCompany)
  )

val addressLens: Lens[Company, Address] =
  Lens(
    c => c.address,
    newAddress => company => company.copy(address = newAddress)
  )

val streetLens: Lens[Address, Street] =
  Lens(
    a => a.street,
    newStreet => address => address.copy(street = newStreet)
  )

val streetNameLens: Lens[Street, String] =
  Lens(
    s => s.name,
    newName => street => street.copy(name =  newName)
  )

val employeeToStreetNameLens: Lens[Employee, String] =
    companyLens >>> addressLens >>> streetLens >>> streetNameLens

employeeToStreetNameLens.get(employee) == "high street"

employeeToStreetNameLens.set("High Street")(employee)
// res1: Employee = Employee(john,Company(awesome inc,Address(london,Street(23,High Street))))

employeeToStreetNameLens.modify(existingStreetName => existingStreetName.head.toString.toUpperCase ++ existingStreetName.tail)(employee)
//res2: Employee = Employee(john,Company(awesome inc,Address(london,Street(23,High street))))

See https://julien-truffaut.github.io/Monocle/optics for more information

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment