Alexey Shuksto
Self-documented.
Эффекты, трамплины и зачем нам это надо?!
a^2 + b^2 = c^2 => c = sqrt(a^2 + b^2)
def pyth(a: Double, b: Double): Double =
require(a > 0)
require(b > 0)
var result = 0.0
result += math.pow(a, 2.0)
result += math.pow(b, 2.0)
math.sqrt(result)
pyth(3.0, 4.0) = 5.0
def squared(a: Double): Option[Double] =
Option.when(a > 0)(math.pow(a, 2.0))
def pyth(a: Double, b: Double): Option[Double] =
squared(a) match
case None => None
case Some(a) =>
squared(b) match
case None => None
case Some(b) => Some(math.sqrt(a + b))
end pyth
pyth(3.0, 4.0) = Some(5.0)
def squared(a: Double): Option[Double] =
Option.when(a > 0)(math.pow(a, 2.0))
def pyth(a: Double, b: Double): Option[Double] =
squared(a) match
case None => None
case Some(a) =>
squared(b) match
case None => None
case Some(b) => Some(math.sqrt(a + b))
end pyth
trait Option[A] { self =>
def flatMap[B](f: A => Option[B]): Option[B] =
self match {
case None => None
case Some(a) => f(a)
}
}
def squared(a: Double): Option[Double] =
Option.when(a > 0)(math.pow(a, 2.0))
def pyth(a: Double, b: Double): Option[Double] =
squared(a).flatMap { a =>
squared(b).flatMap { b =>
Some(math.sqrt(a + b))
}
}
pyth(3.0, 4.0) = Some(5.0)
val list = List(1 to 100: _*)
val foo(i: Int): List[Int] =
List(i to 0 by -1: _*)
val l2 = for (i <- list) yield i + 1
val l3 = list.map(i => i + 1)
assert(l2 == l3)
val l4 = for
i <- list
j <- foo(i)
yield i * j
val l5 = list.flatMap { i =>
foo(i).map(j => i + j)
}
assert(l4 == l5)
def squared(a: Double): Option[Double] =
Option.when(a > 0)(math.pow(a, 2.0))
def pyth(a: Double, b: Double): Option[Double] =
squared(a).flatMap { a =>
squared(b).flatMap { b =>
Some(math.sqrt(a + b))
}
}
def squared(a: Double): Option[Double] =
Option.when(a > 0)(math.pow(a, 2.0))
def pyth(a: Double, b: Double): Option[Double] =
for
a <- squared(a)
b <- squared(b)
yield math.sqrt(a + b)
pyth(3.0, 4.0) = 5.0
def squared(a: Double): Option[Double] =
require(a > 0)
math.pow(a, 2.0)
def pyth(a: Double, b: Double): Double =
val a2 = squared(a)
val b2 = squared(b)
math.sqrt(a2 + b2)
pyth(3.0, 4.0) = 5.0
def squared(a: Double): Option[Double] =
Option.when(a > 0)(math.pow(a, 2.0))
def pyth(a: Double, b: Double): Option[Double] =
for
a <- squared(a)
b <- squared(b)
yield math.sqrt(a + b)
Было бы здорово иметь интерфейс, который определял бы "универсальные"
`.map`, `.flatMap`, `.foreach`...
Cats, Scalaz и все-все-все
trait Functor[F[_]]:
extension [A](fa: F[A])
def map[B](f: A => B): F[B]
trait Applicative[F[_]] extends Functor[F]:
def pure[A](a: A): F[A]
trait Monad[F[_]] extends Applicative[F]:
extension [A](fa: F[A])
def flatMap[B](f: A => F[B]): F[B]
def map[B](f: A => B): F[B] =
flatMap(a => pure(f(a)))
def squared(a: Double): Option[Double] =
Option.when(a > 0)(math.pow(a, 2.0))
def pyth(a: Double, b: Double): Option[Double] =
for
a <- squared(a)
b <- squared(b)
yield math.sqrt(a + b)
def squared[F[_]: Applicative](a: Double): F[Double] =
math.pow(a, 2.0).pure[F]
def pyth[F[_]: Monad](a: Double, b: Double): F[Double] =
for
a2 <- squared[F](a)
b2 <- squared[F](b)
yield math.sqrt(a2 + b2)
pyth[Option](3.0, 4.0) = Some(5.0)
def positive[F[_]: ApplicativeThrow](
a: Double
): F[Unit] = ApplicativeThrow[F]
.raiseWhen(a <= 0) {
java.lang.IllegalArgumentException(
s"Non positive value: $a"
)
}
def pythChecked[F[_]: MonadThrow](
a: Double,
b: Double
): F[Double] = for
_ <- positive[F](a)
_ <- positive[F](b)
c <- pyth[F](a, b)
yield c
pythChecked[Either[Throwable, _]](3.0, 4.0) =
Right(5.0)
pythChecked[Either[Throwable, _]](-3.0, 4.0) =
Left(java.lang.IllegalArgumentException(
Non positive value: -3.0
))
def squared[F[_]: Applicative](a: Double): F[Double] =
math.pow(a, 2.0).pure[F]
def pyth[F[_]: Monad](a: Double, b: Double): F[Double] =
for
a2 <- squared[F](a)
b2 <- squared[F](b)
yield math.sqrt(a2 + b2)
def squared(a: Double): Option[Double] =
Option.when(a > 0)(math.pow(a, 2.0))
def pyth(a: Double, b: Double): Option[Double] =
(squared(a), squared(b)) match {
(Some(a2), Some(b2)) => Some(math.sqrt(a2 + b2))
(None, None) => None
}
def squared[F[_]: Applicative](a: Double): F[Double] =
math.pow(a, 2.0).pure[F]
def pyth[F[_]: Applicative](
a: Double, b: Double
): F[Double] =
(squared[F](a), squared[F](b)).mapN { (a2, b2) =>
math.sqrt(a2 + b2)
}
pyth[Option](3.0, 4.0) = Some(5.0)
def pythChecked(
a: Double, b: Double
): Validated[NonEmptyChain[Throwable], Double] =
(
positive[Either[Throwable, _]](a).toValidatedNec,
positive[Either[Throwable, _]](b).toValidatedNec,
).tupled *>
pyth(a, b)
pythChecked(3.0, 4.0) = Valid(5.0)
pythChecked(-3.0, -4.0) = Invalid(
Chain(
java.lang.IllegalArgumentException(..),
java.lang.IllegalArgumentException(..)
)
)
Часть логики программы, обладающая средствами композиции с другими такими частями
def andThen[A, B, C](f: A => B, g: B => C): A => C =
f.andThen(g)
def flatMap[F[_]: Monad, A, B](fa: F[A])(f: A => F[B]): F[B] =
fa.flatMap(f)
def ap[F[_]: Applicative, A, B](fa: F[A])(ff: F[A => B]): F[B] =
fa.ap(ff)
def hasEven(is: Iterable[Int]): Boolean =
val iter = is.iterator
while iter.hasNext do
if iter.next % 2 == 0
then return true
false
hasEven(Some(1)) = false
hasEven(List(1, 3, 5, 20)) = true
hasEven(LazyList.continually(Random.nextInt(1024))) = true
def hasEven(is: Iterable[Int]): Boolean =
is.foldLeft(false) { (acc, i) =>
acc || (i % 2 == 0)
}
hasEven(Some(1)) = false
hasEven(List(1, 3, 5, 20)) = true
hasEven(LazyList.continually(Random.nextInt(1024))) = ...
// OutOfMemoryError
def hasEven(is: Iterable[Int]): Boolean =
is.foldRight(false) { (i, acc) =>
(i % 2 == 0) || acc
}
hasEven(Some(1)) = false
hasEven(List(1, 3, 5, 20)) = true
hasEven(LazyList.continually(Random.nextInt(1024))) = ...
// StackOverflowError
def hasEven(is: Iterable[Int]): Boolean =
@tailrec
def loop(rest: Iterable[Int]): Boolean =
if rest.isEmpty then false
else if rest.head % 2 == 0 then true
else loop(rest.tail)
loop(is)
end hasEven
hasEven(Some(1)) = false
hasEven(List(1, 3, 5, 20)) = true
hasEven(LazyList.continually(Random.nextInt(1024))) = true
def pow2[R](a: Double): (Double => R) => R =
next => next(math.pow(a, 2.0))
def sqrt[R](a: Double): (Double => R) => R =
next => next(math.sqrt(a))
def add[R](a: Double, b: Double): (Double => R) => R =
next => next(a + b)
def pyth[R](a: Double, b: Double): (Double => R) => R =
next => pow2(a) { a2 =>
pow2(b) { b2 =>
add(a2, b2) { anb =>
sqrt(anb)(next)
}
}
}
pyth(3.0, 4.0)(identity) = 5.0
type Cont[R, A] = (A => R) => R
object Cont:
def apply[R, A](f: (A => R) => R): Cont[R, A] =
f
def pure[R, A](a: A): Cont[R, A] =
apply(cb => cb(a))
extension [R, A](cont: Cont[R, A])
def map[B](f: A => B): Cont[R, B] = Cont { cb =>
cont(f.andThen(cb))
}
def flatMap[B](f: A => Cont[R, B]): Cont[R, B] = Cont { cb =>
val run: Cont[R, B] => R = c => c(cb)
cont(f.andThen(run))
}
end extension
def pow2[R](a: Double): (Double => R) => R =
next => next(math.pow(a, 2.0))
def sqrt[R](a: Double): (Double => R) => R =
next => next(math.sqrt(a))
def add[R](a: Double, b: Double): (Double => R) => R =
next => next(a + b)
def pyth[R](a: Double, b: Double): (Double => R) => R =
next => pow2(a) { a2 =>
pow2(b) { b2 =>
add(a2, b2) { anb =>
sqrt(anb)(next)
}
}
}
pyth(3.0, 4.0)(identity) = 5.0
def pow2[R](a: Double): Cont[R, Double] =
cb => cb(math.pow(a, 2.0))
def sqrt[R](a: Double): Cont[R, Double] =
cb => cb(math.sqrt(a))
def add[R](a: Double, b: Double): Cont[R, Double] =
cb => cb(a + b)
def pyth[R](a: Double, b: Double): Cont[R, Double] = for
a2 <- pow2(a)
b2 <- pow2(b)
anb <- add(a2, b2)
c <- sqrt(anb)
yield c
pyth(3.0, 4.0)(identity) = 5.0
type Cont[R, A] = (A => R) => R
object Cont:
def callCC[R, A, B](
f: (A => Cont[R, B]) => Cont[R, A]
): Cont[R, A] = apply { cb =>
val cont = f(a => apply(_ => cb(a)))
cont(cb)
}
def pythChecked[R](a: Double, b: Double): Cont[R, Option[Double]] =
Cont.callCC((exit: Option[Double] => Cont[R, Option[Double]]) =>
if a <= 0 || b <= 0 then exit(None)
else pyth(a, b).map(Some(_))
)
pyth(3.0, 4.0)(identity) = Some(5.0)
pyth(-3.0, 4.0)(identity) = None
def hasEven[R](is: Iterable[Int]): Cont[R, Boolean] =
Cont.callCC { (exit: Boolean => Cont[R, Boolean]) =>
if is.isEmpty then exit(false)
else if is.head % 2 == 0 then exit(true)
else hasEven(is.tail)
}
hasEven(Some(1))(identity) = false
hasEven(List(1, 3, 5, 20))(identity) = true
hasEven(
LazyList.continually(Random.nextInt(1024))
)(identity) = true
Тип данных, позволяющий представить композицию функций как композицию монад.
Плюс отложенное выполнение.
sealed trait Eval[A]:
def result: Either[Throwable, A]
def value: A = result match
case Right(value) => value
case Left(err) => throw err
def map[B](f: A => B): Eval[B]
def flatMap[B](f: A => Eval[B]): Eval[B]
object Eval:
def now[A](a: A): Eval[A] = ???
def later[A](thunk: => A): Eval[A] = ???
def fail[A](err: Throwable): Eval[A] = ???
object Eval:
def now[A](a: A): Eval[A] = Now(a)
def fail[A](err: Throwable): Eval[A] = Failure(err)
def later[A](thunk: => A): Eval[A] =
Later(() => thunk)
class Now[A](override val value: A) extends Eval[A]:
def result = Right(value)
class Failure[A](val err: Throwable) extends Eval[A]:
def result = Left(err)
class Later[A](thunk: () => A) extends Eval[A]:
def result: Either[Throwable, A] =
try Right(thunk())
catch case NonFatal(err) => Left(err)
end Later
end Eval
sealed trait Eval[A]:
def map[B](f: A => B): Eval[B] =
flatMap(a => later(f(a)))
def flatMap[B](f: A => Eval[B]): Eval[B]
object Eval:
abstract class FlatMap[A] extends Eval[A]:
type Head
val head: () => Eval[Head]
val tail: Either[Throwable, Head] => Eval[A]
def result: Either[Throwable, A] = ???
end FlatMap
sealed trait Eval[A]:
self =>
def map[B](f: A => B): Eval[B] =
flatMap(a => later(f(a)))
def flatMap[B](f: A => Eval[B]): Eval[B] = self match
case fm: FlatMap[A] =>
new FlatMap[B]:
type Head = fm.Head
val head = fm.head
val tail = hd =>
new FlatMap[B]:
type Head = A
val head = () => fm.tail(hd)
val tail = _.fold(fail, f)
case _ =>
new FlatMap[B]:
type Head = A
val head = () => self
val tail = _.fold(fail, f)
end flatMap
object Eval:
abstract class FlatMap[A] extends Eval[A]:
def result: Either[Throwable, A] = evaluate(this)
private[Eval] sealed trait Stack[A, B]
private[Eval] final class One[A, B](val f: A => B)
extends Stack[A, B]
private[Eval] final class Many[A, B, C](
val head: Either[Throwable, A] => Eval[B],
val tail: Stack[B, C]
) extends Stack[A, C]
private[Eval] def evaluate[A](eval: Eval[A]): Either[Throwable, A] =
@tailrec def loop[A1](
eval: Eval[A1],
stack: Stack[A1, A]
): Either[Throwable, A] = ???
loop(eval, One(identity))
end evaluate
end Eval
object Eval:
def evaluate[A](eval: Eval[A]): Either[Throwable, A] =
@tailrec def loop[A1](
eval: Eval[A1], stack: Stack[A1, A]
): Either[Throwable, A] = eval match
case f: Failure[A1] =>
Left(f.err)
case fm: FlatMap[A1] => fm.head() match
case f: Failure[A1] =>
Left(f.err)
case fm1: FlatMap[fm.Head] =>
loop(fm1.head(), Many(fm1.tail, Many(fm.tail, stack)))
case inner =>
loop(fm.tail(inner.result), stack)
// leafs: Now, Later, Failure
case _ => stack match
case o: One[A1, A] =>
eval.result.map(o.f)
case m: Many[A1, b, A] =>
loop(m.head(eval.result), m.tail)
loop(eval, One(identity))
end evaluate
def hasEven(is: Iterable[Int]): Eval[Boolean] =
Eval.later(is.isEmpty).flatMap { empty =>
if empty then Eval.now(false)
else if is.head % 2 == 0 then Eval.now(true)
else hasEven(is.tail)
}
val eval = hasEven(
LazyList.continually(Random.nextInt(1024))
)
eval.result = Right(true)
def foldRight[A, B](ia: Iterable[A], zero: Eval[B])(
f: (A, Eval[B]) => Eval[B]
): Eval[B] =
def loop(ia: Iterable[A]): Eval[B] =
Eval.later(ia.isEmpty).flatMap {
case true => zero
case false => f(ia.head, loop(ia.tail))
}
loop(ia)
end foldRight
def hasEven(is: Iterable[Int]): Eval[Boolean] =
foldRight(is, Eval.now(false)) { (i, acc) =>
if i % 2 == 0 then Eval.now(true)
else acc
}
def hasEven(is: Iterable[Int]): Eval[Boolean] =
foldRight(is, Eval.now(false)) { (i, acc) =>
if i % 2 == 0 then Eval.now(true)
else acc
}
val eval = hasEven(
LazyList.continually(Random.nextInt(1024))
)
eval.result = Right(true)
def isEven(i: Int): Eval[Boolean] =
Eval.later(i == 0).flatMap {
case true => Eval.now(true)
case false => isOdd(i - 1)
}
def isOdd(i: Int): Eval[Boolean] =
Eval.later(i == 0).flatMap {
case true => Eval.now(false)
case false => isEven(i - 1)
}
isEven(100501).value = false
isOdd(100501).value = true
expression
+ terms
- terms
terms
terms
term + terms
term - terms
term
term
factor
factor * term
factor / term
primary
( expression )
number
factor
primary
primary ^ factor
object Calc:
case class Parsed(result: Int, leftovers: List[String])
def expression(tokens: List[String]): Eval[Parsed] =
tokens match
case "-" :: tail => terms(tail).map(p => p.copy(result = -p.result))
case "+" :: tail => terms(tail)
case _ => terms(tokens)
def terms(tokens: List[String]): Eval[Parsed] = ???
def term(tokens: List[String]): Eval[Parsed] = ???
def factor(tokens: List[String]): Eval[Parsed] = ???
def primary(tokens: List[String]): Eval[Parsed] = tokens match
case "(" :: tail => expression(tail).flatMap {
case Parsed(r, ")" :: tail) => Eval.now(Parsed(r, tail))
case _ => Eval.fail(...)
}
case head :: tail => Eval.later(Parsed(head.toInt, tail))
case _ => Eval.fail(...)
Calc("1").result = Right(1)
Calc("1 + 1").result = Right(2)
Calc("2 * 2").result = Right(4)
Calc("( 1 + 3 ) * 4 - ( 6 / ( 3 ^ 0 + 1 ) )").result =
Right(13)
Calc("( 1 + 3 ) * 4 - ( 6 / ( 3 ^ 0 + 1 )").result =
Left(java.lang.IllegalArgumentException(..))
Методика, позволяющая представить потенциально бесконечную (в т.ч. рекурсивную) последовательность вызова функций в виде ленивой и стеко-безопасной монадической композиции этих самых функций.
By Alexey Shuksto
Что такое эффекты, трамплины и зачем они нам нужны?