Enterprise Algebras, Scala World 2016

Post on 12-Jan-2017

1.655 views 0 download

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