Enterprise Algebras, Scala World 2016
-
Upload
timothy-perrett -
Category
Technology
-
view
1.655 -
download
0
Transcript of Enterprise Algebras, Scala World 2016
ENTERPRISE ALGEBRASTimothy Perrett
Scala World 2016
HELLO.*waves*
“Tuesday”
WE’RE HIRING!
WE ❤ FP.It’s not just for academics!
OSS.github.com/verizon
~1000 REPOS.~ 85% are Scala projects (yes, built with SBT).
11 SBT PLUGINS.Doing the nasty so users don't have too.
SUCH SIZE. SO WOW.Requires scaling and automation of all the things.
METHOD.Constraints liberate. Liberties constrain.
METHOD.One uses frameworks. Frameworks use you.
Invest in the future, today.
EDUCATION.
EDUCATION.
➤ Evangelize functional programming benefits to team.
➤ Users initially self-directed learning from texts and online resources.
➤ Dissemination of techniques from staff (typically the team lead or coworkers)
➤ Practice. Practice. Practice. Pull requests provide a path to learn every day.
➤ Internal courses and materials for learning more advanced FP techniques.
➤ Further practice and experience. Rinse and repeat :-)
REAL-WORLD FPit actually does work in production.
HLIST“seems like a good idea”
HLIST.
➤ It’s THE gateway drug for advanced Scala.
➤ Easy to understand.
➤ Untold numbers of business uses.
➤ One of the most used third-party construct across our internal eco-system.
FREE & COYONADA.
FREE.Certainly not an exhaustive guide.
sealed trait Free[F[_],A]
case class Return[F[_],A](a: A) extends Free[F,A]
case class Suspend[F[_],A](s: F[Free[F, A]]) extends Free[F,A]
case class Return[F[_],A](a: A) extends Free[F,A]
case class Suspend[F[_],A](s: F[Free[F, A]]) extends Free[F,A]
case class Return[F[_],A](a: A) extends Free[F,A]
case class Suspend[F[_],A](s: F[Free[F, A]]) extends Free[F,A]
THE YONEDA LEMMA.
def apply[B](f: A => B): F[B]
abstract class Yoneda[F[_], A]{ def apply[B](f: A => B): F[B]}
def toYoneda[F[_] : Functor, A](fa: F[A]) = new Yoneda[F,A] { def apply[B](f: A => B) = Functor[F].map(fa)(f) }
def fromYoneda[F[_], A](yo: Yoneda[F, A]) = yo.apply(identity)
SO WHAT?
sealed abstract class Coyoneda[F[_], A]{ type I val fi: F[I] val k: I => A}
sealed abstract class Coyoneda[F[_], A]{ type I val fi: F[I] val k: I => A}
YAY!
case class Suspend[F[_],A](s: F[Free[F, A]]) extends Free[F,A]
type FreeC[S[_], A] = Free[({type f[x] = Coyoneda[S, x]})#f, A]
NOMAD MONAD.Fun to say. Better to use.
sealed abstract class SchedulerOp[A] extends Product with Serializable
final case class Delete(…) extends SchedulerOp[Unit]
final case class Launch(…) extends SchedulerOp[Unit]
type SchedulerF[A] = Free.FreeC[SchedulerOp, A]
def delete(…): SchedulerF[Unit] = Free.liftFC(Delete(x,y))
SchedulerOp ~> Task
def apply[A](s: SchedulerOp[A]): Task[A]
SchedulerOp ~> Task
def apply[A](s: SchedulerOp[A]): Task[A]TESTABLE
CO-PRODUCTS.
for { a <- free1.bar(123) b <- free2.foo(a)} yield b
for { a <- free1.bar(123) b <- free2.foo(a)} yield b
final case class Coproduct[F[_], G[_], A]( run: F[A] \/ G[A])
final case class Coproduct[F[_], G[_], A]( run: F[A] \/ G[A])
algebra 1
algebra 2
type CombOp[A] = Coproduct[QuxOp, FooOp, A]
type CombF[A] = Free.FreeC[CombOp, A]
def example: CombF[String] = for { a <- useQux(1) b <- useFoo(a) } yield b
def useQux(x: Int): CombF[Int] = QuxOp.gogogo(x).inject
def useFoo(x: Int): CombF[String] = FooOp.gogogo(x).inject
final implicit class InjectCombF[F[_],A]( val fa: Free.FreeC[F,A])( implicit I: Inject[F, Comb.CombOp]){ def inject[G[_]] = injectFC.apply(fa)}
def injectFC[F[_], G[_]](implicit I: Inject[F, G]): FreeC[F, ?] ~> FreeC[G, ?] = new (FreeC[F, ?] ~> FreeC[G, ?]) { def apply[A](fa: FreeC[F, A]): FreeC[G, A] = fa.mapSuspension[Coyoneda[G, ?]]( new (Coyoneda[F, ?] ~> Coyoneda[G, ?]) { def apply[B](fa: Coyoneda[F, B]): Coyoneda[G, B] = fa.trans(I) } ) }
sorry, there aren’t really ways to simplify this.
def injectFC[F[_], G[_]](implicit I: Inject[F, G]): FreeC[F, ?] ~> FreeC[G, ?] = new (FreeC[F, ?] ~> FreeC[G, ?]) { def apply[A](fa: FreeC[F, A]): FreeC[G, A] = fa.mapSuspension[Coyoneda[G, ?]]( new (Coyoneda[F, ?] ~> Coyoneda[G, ?]) { def apply[B](fa: Coyoneda[F, B]): Coyoneda[G, B] = fa.trans(I) } ) }
transform free algebras
sorry, there aren’t really ways to simplify this.
def injectFC[F[_], G[_]](implicit I: Inject[F, G]): FreeC[F, ?] ~> FreeC[G, ?] = new (FreeC[F, ?] ~> FreeC[G, ?]) { def apply[A](fa: FreeC[F, A]): FreeC[G, A] = fa.mapSuspension[Coyoneda[G, ?]]( new (Coyoneda[F, ?] ~> Coyoneda[G, ?]) { def apply[B](fa: Coyoneda[F, B]): Coyoneda[G, B] = fa.trans(I) } ) }
sorry, there aren’t really ways to simplify this.
transform coyonedas
def injectFC[F[_], G[_]](implicit I: Inject[F, G]): FreeC[F, ?] ~> FreeC[G, ?] = new (FreeC[F, ?] ~> FreeC[G, ?]) { def apply[A](fa: FreeC[F, A]): FreeC[G, A] = fa.mapSuspension[Coyoneda[G, ?]]( new (Coyoneda[F, ?] ~> Coyoneda[G, ?]) { def apply[B](fa: Coyoneda[F, B]): Coyoneda[G, B] = fa.trans(I) } ) }
sorry, there aren’t really ways to simplify this.
F[A] => G[A]
APPLICATION DESIGN.
short-lived request response
free algebra
coproduct algebra
alternitivly, use kleisli
edge of the world
long-lived stream
edge of the world
case GET -> Root / "v1" / "datacenters" / dcname & IsAuthenticated(session) => jsonF(Nelson.fetchDatacenterByName(dcname)){ option => option match { case Some(dc) => Ok(dc.asJson) case None => NotFound(s"datacenter '$dcname' does not exist") } }
sorry, there aren’t really ways to simplify this.
protocol specifics
case GET -> Root / "v1" / "datacenters" / dcname & IsAuthenticated(session) => jsonF(Nelson.fetchDatacenterByName(dcname)){ option => option match { case Some(dc) => Ok(dc.asJson) case None => NotFound(s"datacenter '$dcname' does not exist") } }
sorry, there aren’t really ways to simplify this.
domain function
BlazeBuilder .bindHttp(8080, 127.0.0.1) .mountService(service) .start // Task[Server] .run // Unit
sorry, there aren’t really ways to simplify this.
APPLICATION DESIGN.
➤ All your I/O boundaries - from a systems perspective - should be a Free algebra.
➤ Database access.
➤ Network access.
➤ Any other random things you can’t sensibly reason about.
➤ Be extremely selective about the edge of the world.
➤ Understand the semantics of running your Task.
TECHNICAL DEBT.
TECHNICAL DEBT
➤ Accept it will happen. Minimization is the best you can hope for.
➤ Proactively push debt repositories to the leaves of your system graph.
➤ Libraries are the key battleground in debt avoidance. Choose wisely.
➤ Be consistent with different library APIs; this drives adoption and avoids accidental debt when users do the unexpected.
CHALLENGES.
CHALENGES.
➤ Scala is not a pure FP language.
➤ Ironically a positive and enabling thing for both beginners, and the business.
➤ Community libraries can be a mixed bag.
➤ Not everyone prioritizes maintaining binary compatible releases.
➤ Many internal forks to stabilize deps.
➤ GC is a real and present danger.
➤ Ensuring users don’t revert to Java without the semicolons is a definite and on-going challenge.
* Both fleshy and technical challenges* Generally speaking, hiring for Scala has not been a big issue.
WINS.
WINS.
➤ Doing FP has allowed us to recruit and retain some brilliant minds.
➤ The application of Free, Cofree, Fix and other advanced type-level paradigms make your software easier to refactor, and cheaper over time.
➤
CONCLUSIONS.
CONCLUSIONS.
➤ Free & Coproduct allow us to build modular, testable, systems from discrete programs.
➤ Your program has programs.
➤ Running Scala at scale is not zero-cost. Far from it.
➤ You need expertise in the JVM to understand corner cases (happens for Java too).
➤ Purely functional stream programming currently struggles at high-load due to GC pauses.
➤ Team education is a hard problem.
➤ Don’t hire for quantity. Hire for quality and aptitude.
SCALA CENTER.https://github.com/scalacenter/advisoryboard/pulls
we need the community!
EOFtimperrettgithub.com/verizon