object Execution {
implicit class Ops[A, C[_]](c: C[A]) {
def flatMap[B](f: A => C[B])(implicit e: Execution[C]): C[B] =
e.doAndThen(c)(f)
def map[B](f: A => B)(implicit e: Execution[C]): C[B] =
e.doAndThen(c)(f andThen e.create)
}
}
def echo[C[_]](implicit t: Terminal[C], e: Execution[C]): C[String] =
t.read.flatMap { in: String =>
t.write(in).map { _: Unit =>
in
}
}
This way, we define method flatMap on objects of type C[A]
if Execution is implicit.
scala> import scala.reflect.runtime.universe._
scala> val a, b, c = Option(1)
scala> show { reify {
for { i <- a ; j <- b ; k <- c } yield (i + j + k)
} }
res:
$read.a.flatMap(
((i) => $read.b.flatMap(
((j) => $read.c.map(
((k) => i.$plus(j).$plus(k)))))))
reify> for { i: Int <- a } yield i
a.withFilter {
case i: Int => true
case _ => false
}.map { case i: Int => i }
scala> def getC: Future[Int] = ...
scala> def getD: Option[Int] = ...
scala> val result = for {
a <- OptionT(getA)
b <- OptionT(getB)
c <- getC.liftM[OptionT]
d <- OptionT(getD.pure[Future])
} yield (a * b) / (c * d)
result: OptionT[Future, Int] = OptionT(Future(<not completed>))
|>
(thrush operator) applies the function on the right on the value on the left.
scala> val result = for {
a <- getA |> liftFutureOption
b <- getB |> liftFutureOption
c <- getC |> liftFuture
d <- getD |> liftOption
e <- 10 |> lift
} yield e * (a * b) / (c * d)
result: OptionT[Future, Int] = OptionT(Future(<not completed>))
def initial: F[WorldView] =
^^^^(D.getBacklog, D.getAgents, M.getManaged, M.getAlive, M.getTime) {
case (db, da, mm, ma, mt) => WorldView(db, da, mm, ma, Map.empty, mt)
}
// Or:
// (D.getBacklog |@| D.getAgents |@| M.getManaged |@| M.getAlive |@| M.getTime)
This is supported by the Future monad but not the Id monad.
The compiler will not perform exhaustivity checking if the class is not sealed or if there are guards, like =case Foo(bar) if bar => “cool”= To remain safe, don’t use guards on sealed types.
A cleaner syntax to define nested Either types is to create an alias type ending with a colon, allowing infix notation with association from the right:
type |:[L,R] = Either[L, R]
X.type |: Y.type |: Z
type Accepted = String |: Long |: Boolean
In FP, functions are total and must return an instance for every input, no Exception. Minimising the complexity of inputs and outputs is the best way to achieve totality. As a rule of thumb, it is a sign of a badly designed function when the complexity of a function’s return value is larger than the product of its inputs: it is a source of entropy.
(A => C) => ((B => C) => C)
We can convert and rearrange
(c ^ (c ^ b)) ^ (c ^ a)
= c ^ ((c ^ b) * (c ^ a))
= c ^ (c ^ (a + b))
then convert back to types and get
(Either[A, B] => C) => C
which is much simpler: we only need to ask the users of our framework to provide a Either[A, B] => C.
implicit class DoubleOps(x: Double) {
def sin: Double = math.sin(x)
}
1.0.sin
The above has a runtime effect since it compiles to this and creates a DoubleOps for no long term use:
implicit def DoubleOps(x: Double): DoubleOps = new DoubleOps(x)
class DoubleOps(x: Double) {
def sin: Double = java.lang.Math.sin(x)
}
Prefer this new version:
implicit final class DoubleOps(val x: Double) extends AnyVal {
def sin: Double = java.lang.Math.sin(x)
}
Fancy way using Simulacrum. This allows using the T: Numeric
trait directly.
import simulacrum._
@typeclass trait Ordering[T] {
def compare(x: T, y: T): Int
@op("<") def lt(x: T, y: T): Boolean = compare(x, y) < 0
@op(">") def gt(x: T, y: T): Boolean = compare(x, y) > 0
}
@typeclass trait Numeric[T] extends Ordering[T] {
@op("+") def plus(x: T, y: T): T
@op("*") def times(x: T, y: T): T
@op("unary_-") def negate(x: T): T
def zero: T
def abs(x: T): T = if (lt(x, zero)) negate(x) else x
}
import Numeric.ops._
def signOfTheTimes[T: Numeric](t: T): T = -(t.abs) * t
But usually we do this:
trait Ordering[T] {
def compare(x: T, y: T): Int
def lt(x: T, y: T): Boolean = compare(x, y) < 0
def gt(x: T, y: T): Boolean = compare(x, y) > 0
}
And use like this:
def signOfTheTimes[T](t: T)(implicit N: Numeric[T]): T = {
import N._
times(negate(abs(t)), t)
}
Instances are defined as:
implicit val NumericDouble: Numeric[Double] = new Numeric[Double] {
def plus(x: Double, y: Double): Double = x + y
...
def compare(x: Double, y: Double): Int = java.lang.Double.compare(x, y)
// optimised
override def lt(x: Double, y: Double): Boolean = x < y
...
}