FP OOP

F

O

P

Процедуры

Процедуры

PROCEDURE update_user_name(
    id    USER_ID,
    name  STRING
) 
BEGIN.
  VAR user         USER.
  VAR current_time TIMESTAMP.
  
  READ users INTO user WITH ID id.
  SET user.name TO name.
  GET TIME STAMP TO current_time.
  SET user.changed TO current_time.
  WRITE users FROM user WITH ID id.
END.

Процедуры

PROCEDURE set_user_changed_now(
	user  USER
).
BEGIN.
  VAR current_time TIMESTAMP.
  GET TIME STAMP TO current_time.
END

PROCEDURE update_user_name(
    id    USER_ID,
    name  STRING
) 
BEGIN.
  VAR user         USER.
  
  READ users INTO user WITH ID id.
  SET user.name TO name.
  CALL set_user_changed_now( user )
  WRITE users FROM user WITH ID id.
END.
SET a = b * 2 + 345.
SET full_name = firstName ++ " " ++ lastName.
SET capitalized = length( s ) > 0 && 
                  isUpperCase( s[0] ).

READ users TO user.
WRITE users FROM user.
GET TIME STAMP TO now.
PRINT 'Hello World!'.

FOR i = 1 TO 10 STEP 2.
 ...
END.

IF x > 2.
  ...
ELSE.
  ...
END.

TRY.
  ...
CATCH X.
  ...
FINALLY.
  ...
END.

Операторы

Команды

Контроль

исполнения

Проблемы

  1. Глобальные переменные
  2. Пред- и пост- условия
  3. Изменения в зависимых блоках
PROCEDURE SAVE_USER( user USER )
BEGIN.

  SET user.id = g_next_id.
  ADD 1 TO g_next_id.
  
  IF user.name IS EMPTY.
    THROW USER_NAME_IS_EMPTY.
  END.
  
  LOOP AT user.friends INTO friend.
    CALL remove_frient_if_incorrent( user, friend ).
  ENDLOOP.
  
  CALL initialize_auth_data ( user ).
  
  UPDATE users FROM user.
END.
  

Объекты

ООП

def updateUserName(name: String): Unit = {
    this.name = name
    this.changed = time.now()
    this.save()
}
  • Сохраняет процедурный стиль написания методов
  • Не использует команды. Только методы
  • Разрешает прямой доступ к состоянию только методам этого объекта

Single-responsibility principle

Каждый класс или модуль выполняет ровно одну задачу

GRASP

DDD

  • Данные
  • Утилиты
  • Процессы

Данные

  • Entity
  • Value object
  • Event

 

POJO

trait User{
    def getName: String
    def setName(s: String): Unit
    
    def getId: String
    
    def getFriends: List[User]
    def addFriend(user: User): Unit
    def removeFriend(user: User): Unit
}
case class User(id: String, name: String, friends: List[User])

Утилиты

  • Pure Fabrication (GRASP)
  • Service (DDD)

 

trait Iterator[A]{
    def next: Option[A]
}

trait Decorator[A]{
    def decorate(obj: A): A
}

trait Comparator[A]{
    def compare(x: A, y: A): Int
}

trait Predicate[A]{
    def check(x : A) : Boolean
}

Процессы

  • Factory
  • Repository
trait Users{
    def getUser(id: String): User
    def remove(user: User): Unit
}

trait Rules{
    def validate(expr: String): Rule
    def activate(rule: Rule): Unit
}
  • Controller
  • Session
  • Facade

Функции

Операторы

val commissionQ = 1 + commissionPercents / 100
val newBalance = oldBalance + income * commissionQ
val estimatedDuration = elapsedTime
                         .multipliedBy(doneCount)
                         .dividedBy(totalCount)
                         
val estimatedEnd = now.plus(estimatedDuration)
val estimatedDuration = elapsedTime * doneCount / totalCount
val estimatedEnd = now + estimatedDuration
val ruleset = basic.or(privileged.when(user.vip)).and(countryRules)
val ruleset = (basic || privileged ? user.vip) && countryRules

Данные

case class User(id: UserId, name: String, roles: List[Role])
sealed trait User

case object Anonymous extends User
case class External(partner: PartnedId, extId: String) extends User
case class WebUser(id: UserId, name: String, roles: List[RoleId]) extends User
List[User], Vector[User], Set[User], Map[String, User]

Преобразования

type Predicate[A] = A => Boolean

type Decorator[A] = A => A

type Comparator[A] = (A, A) => Ordering

type Iterator[S, A] = S => Option[(S, A)]

Иммутабельность

def setUserName(user: User, name: String): User = 
	user.copy(name = name)

def setUserName(name: String): State[User, Unit] = 
	State.update(_.copy(name = name))

val userName: User Update String = 
	GenContains[User](_.name)

def setActive(user: User, active: Boolean): User = 
    user.copy(
      friends = user.friends.map(_.copy(active = true)), 
      active = true
    )
    
val userActive: User Update Boolean = 
	User.active then (User.friends > every > userActive.delayed)

Полиморфизм

Интерфейсы

PROCEDURE update_user_name( user User, name String )

PROCEDURE update_product_name( product ProductId, company CompanyId, name String )

PROCEDURE update_dog_name( id DogId , name String)
class User{
    def setName(name: String)
}

class Product{
    def setName(name: String)
}

class Dog{
    def setName(name: String)
}
trait Named{
    def getName: String
    def setName(name: String)
}

Параметрический полиморфизм

PROCEDURE update_user_name( user User, name String )

PROCEDURE update_product_name( product ProductId, company CompanyId, name String )

PROCEDURE update_dog_name( id DogId , name String)
def userName: User Contains String

def productName: Product Contains String

def dogName: Product Contains String
trait Named[A]{
    def name: A Contains String
}
trait Named[A]{
    def updateName(f: String => String, old: A): A
    def getName(a: A): String
}

Интерпретация

Консоль

sealed trait Console

case class PrintLn(s: String, next: Console) extends Console
case class ReadLn(continue: String => Console) extends Console
case object End extends Console
// читает две строчки, затем печатает их конкатенацию
val echo2: Console = ReadLn( s1 => ReadLn( s2 => Println(s1 + s2, End) ) )

Паттерн-матчинг

sealed trait Console

case class PrintLn(s: String, next: Console) extends Console
case class ReadLn(continue: String => Console) extends Console
case object End extends Console
def runConsoleS(c: Console, in: Vector[String], out: Vector[String] = Vector()): Vector[String] = 
c match {
  case PrintLn(s, next) => runConsoleS(next, in, out :+ s)
  case ReadLn(k) =>
    in match {
      case s +: rest => runConsoleS(k(s), rest, out)
      case _         => out
    }
  case End => out
}
def runConsole(c: Console): Unit = c match {
    case PrintLn(s, next) => 
    	println(s)
        runConsole(next)
    case ReadLn(k) => runConsole(k(StdIn.readLine()))
    case End => 
}

Типы и Продолжения

sealed trait Console[+A] {
  def flatMap[B](f: A => Console[B]): Console[B] = Continue(this, f)
  def map[B](f: A => B) = flatMap(a => Now(f(a)))
}

case class PrintLn(s: String)                                 extends Console[Unit]
case object ReadLn                                            extends Console[String]
case class Now[+A](a: A)                                      extends Console[A]
case class Continue[A, +B](c: Console[A], f: A => Console[B]) extends Console[B]

val echo2: Console[Unit] = for{
    s1 <- ReadLn
    s2 <- ReadLn
    _  <- PrintLn(s1 + s2)
} yield ()

Консоль

sealed trait Console[+A] {
  def flatMap[B](f: A => Console[B]): Console[B] = Continue(this, f)
  def map[B](f: A => B) = flatMap(a => Now(f(a)))
}

case class PrintLn(s: String)                                 extends Console[Unit]
case object ReadLn                                            extends Console[String]
case class Now[+A](a: A)                                      extends Console[A]
case class Continue[A, +B](c: Console[A], f: A => Console[B]) extends Console[B]

def runConsole[A](c: Console[A]): A = c match {
    case Now(a)     => a
    case ReadLn     => StdIn.readLine()
    case PrintLn(s) => println(s)
    case Continue(c1, f) =>
      c1 match {
        case Now(a) => runConsole(f(a))
        case PrintLn(s) =>
          println(s)
          runConsole(f(()))
        case ReadLn          => runConsole(f(StdIn.readLine()))
        case Continue(c2, g) => runConsole(c2.flatMap(g(_).flatMap(f)))
      }
}

Монада

trait Monad[F[_]]{
    def pure(a: A): F[A]
    def flatMap[A, B](a: F[A], f: A => F[B]): F[B]
}

implicit object consoleMonad extends Monad[Console]{
    def pure(a: A) = Now(a)
    def flatMap[A, B](a: A, f: A => Console[B]) = Continue(a, f)
}
  def printEachThrice[A](elements: List[String]): Console[Unit] = 
    elements.traverse_( a => for{
        _ <- PrintLn(a)
        _ <- PrintLn(a)
        _ <- PrintLn(a)
    } yield ())

    
  def printEven(x: Int): Console[Unit] = 
    if(x % 2 == 0) PrintLn(x.toString)
    else unit[Console]
  def printEachThrice[A](elements: List[String]) =
    elements.traverse_(a => printLn(a).replicateA(3))

  def printEven(x: Int) =
    printLn(x.toString).whenA(x % 2 == 0)

Free Монада

sealed trait ConsoleF[+A]

case class PrintLn(s: String) extends ConsoleF[Unit]
case object ReadLn            extends ConsoleF[String]
  
type Console[A] = Free[ConsoleF, A]

def printLn(s: String): Console[Unit] = Free.liftF(PrintLn(s))
val readLn: Console[String]           = Free.liftF(ReadLn)

val echo2: Console[Unit] = for {
  s1 <- readLn
  s2 <- readLn
  _  <- printLn(s1 + s2)
} yield ()

def runConsoleF[A](c: ConsoleF[A]): A = c match {
  case PrintLn(s) => println(s)
  case ReadLn     => StdIn.readLine()
}

def runConsole[A](c: Console[A]): A = c.foldMap[Id](funK(runConsoleF))

IO

IO

zio.ZIO


cats.effect.IO


monix.eval.Task

Эффекты

IO.pure(1)

IO(println("Hello world!!!"))

IO(runServer())

IO.async(cb => getData(a => cb(Right(a))))

IO.cancelable{ cb => 
    val cancelable = getData(a => cb(Right(a)))
    IO(cancelable.cancel())
}
trait Timer {
  def realTime(unit: TimeUnit): IO[Long]
  def sleep(duration: FiniteDuration): IO[Unit]
}

try ... finally ...

trait IO[+A]{
   //...
   def bracket[B](use: A => F[B])(release: (A, ExitCase[B]) => F[Unit]): F[B]
   //...
}
openFile(name).bracket( file => for {
    line <- file.readLine
    _    <- processLine(line)
}){case (file, _) => file.close} 
OPEN name TO file.

TRY.
    READ LINE OF file TO line.
    CALL process_line( line ).
FINALLY.
    CLOSE file.
END.
  

Concurrency

trait Fiber[+A] {
  def cancel: IO[Unit]
  def join: IO[A]
}

trait IO[+A]{
  //...
  def start: IO[Fiber[A]]
  def racePair[B](rb: IO[B]): IO[Either[(A, Fiber[B]), (Fiber[A], B)]]
  //...
}

for {
  ...
  fiber <- process.start
  ...
   _ <- fiber.cancel
  ...
}


processA.racePair(processB) match {
    case Left((a, bfib)) =>
    case Right((fiba, b)) => 
}

Синхронизация

trait Ref[A]{
   def get: IO[A]
   def set(a: A): IO[Unit]
   def modify[B](f: A => (A, B)): IO[B]
}
abstract class Semaphore {
    def acquireN(n: Long): IO[Unit]
    def releaseN(n: Long): IO[Boolean]
    def withPermit[A](t: IO[A]): IO[A]
}
object Ref{
    def of[A](a: A): IO[Ref[A]]
}
object Semaphore{
  def apply(limit: Long): IO[Semaphore]
}

Фабричные методы

object Ref{
  def of[A](a: A): IO[Ref[A]]
}

object Semaphore{
  def apply(limit: Long): IO[Semaphore]
}

object HttpClient{
  def apply(config: HttpConfig): IO[HttpClient]
}

object Cluster{
  def connect(config: ClusterConfig): IO[Cluster]
}

Зависимость

def saveUser(user: User, db: DB, cache: UserCache): IO[Unit]

Reader

trait Reader[Env, +A]{
   def run(env: Env): IO[A]  
   
   def flatMap[B](f: A => Reader[Env, A]): Reader[Env, A] = 
   	 env => this.run(env).flatMap(a => f(a).run(env))
}

object Reader{
  def pure[Env, A](a : A): Reader[Env, A] = _ => IO.pure(a)
  
  def lift[Env, A](la: IO[A]): Reader[Env, A] = _ => la
}

Контекст

case class Environment(
    config: Config
    opCache: OperationCache,
    users: UserRepo,
    httpClient: HttpClient,
    kafka: KafkaProducer,
)

object Environment{
   def apply(config: Config): IO[Environment] = for {
      opCache     <- OperationCache()
      users       <- UserRepo(config.users)
      httpClient  <- HttpClient(config.http)
      kafka       <- KafkaProducer(config.kafka)
    } yield Environment(
       config = config,
       opCache = opCache,
       users = users,
       httpClient = httpClient,
       kafka = kafka
     )
}
def application: Reader[Environment, Unit]
def execute: IO[Unit] = 
  for{
     config       <- readConfig
     environment  <- Environment(config)
     _            <- application.run(environment).foreverM
  } yield ()

Tagless Final

Интерпретация

def printUserInfo(user: User): Console[Unit]

def promoteToAccout(login: login): Security[Account]

def getActiveFriends(userId: UserId): UserRepo[List[User]]

Ограничения Free

3. Expression Problem

def checkUserAuthorized(user: User): (UserRepo with Security)[Boolean]

def printVerificationInfo(account: Acount): (Console with Verification)[Unit]

1.Low Order

def doOnCommit[A](action: Transaction[A]): Transaction[A]

def forEachProcess[A](f: Process => Parallel[A]): Parallel[Unit]

2. Boilerplate

def printLn(x: String): Console[A] = Free.lift(PrintLn(s))
def getRoles(userId: UserId, time: Instant): Security[List[Role]] = 
	Free.lift(GetRoles(userId, time))

Free : Expression Problem

sealed trait Console

case class PrintLn(s: String, next: Console) extends Console
case class ReadLn(continue: String => Console) extends Console
case object End extends Console

val echo2: Console = ReadLn( s1 => ReadLn( s2 => Println(s1 + s2, End) ) )
trait Console[A]{
    def printLn(s: String, next: A): A
    def readLn(continue: String => A): A
    def end: A
}

def echo2[A](c: Console[A]): A = c.readLn(s1 => c.readLn(s2 => c.println(s1 + s2, c.end)))
object RunConsole extends Console[() => Unit]{
    def printLn(s: String, next: () => Unit) => () => {
    	println(s)
        next()
    }
    def readLn(k: String => () => Unit) => k(StdIn.readLine())()
    def end = () => () 
}

Tagless Final

trait Console[A]{
    def printLn(s: A, next: A): A
    def readLn(continue: A => A): A
    def end: A
}

trait Arith[A]{
    def literal(i : Int) : A
    def parse(x: A): A
    def show(x: A): A
    def plus(x: A, y: A): A
    def times(x: A, y: A): A
}

def printSum[A](c: Console[A], a: Arith[A]): A = 
    c.readLn(s1 => 
       c.readLn(s2 => 
          c.printLn(
             a.show(a.plus(a.parse(s1), a.parse(s2))))))

Expression Problem

trait Console[F[_]]{
    def printLn[A](s: F[String], next: F[A]): F[A]
    def readLn[A](continue: F[String] => F[A]): F[A]
    def end: F[Unit]
}

trait Arith[F[_]]{
    def literal(i : Int) : F[Int]
    def parse(x: F[String]): F[Int]
    def show(x: F[Int]): F[String]
    def plus(x: F[Int], y: F[Int]): F[Int]
    def times(x: F[Int], y: F[Int]): F[Int]
}

def printSum[F[_]](c: Console[F], a: Arith[F]): F[Unit] = 
    c.readLn(s1 => 
       c.readLn(s2 => 
          c.printLn(
             a.show(a.plus(a.parse(s1), a.parse(s2))))))

Типизированые языки

trait Monad[F[_]]{
   def pure[A](a: A): F[A]
   def flatMap[A, B](fa: F[A], f: A => F[B]): F[B]
} 
trait Console[F[_]]{
    def printLn[A](s: String): F[Unit]
    def readLn[A]: F[String]
}
trait Arith[F[_]]{
    def literal(i : Int) : F[Int]
    def parse(x: String): F[Int]
    def show(x: Int): F[String]
    def plus(x: Int, y: Int): F[Int]
    def times(x: Int, y: Int): F[Int]
}

def printSum[F[_]](implicit m: Monad[F], c: Console[F], a: Arith[F]): F[Unit] = 
    for{
    	sx <- c.readLn
        sx <- c.readLn
        x  <- a.parse(sx)
        y  <- a.parse(sy)
        z  <- a.sum(x, y)
        sz <- a.show(z)
        _  <- c.printLn(sz)
    } yield ()

Монады

def printSum[F[_]: Monad: Console: Arith]: F[Unit] = 
    for{
    	sx <- readLn[F]
        sx <- readLn[F]
        x  <- parse[F](sx)
        y  <- parse[F](sy)
        z  <- sum[F](x, y)
        sz <- show[F](z)
        _  <- printLn[F](sz)
    } yield ()

Монады

SOLID

  • Single Responsibility

  • Open Close Principle

  • Liskov Substitution

  • Interface Segregation

  • Dependency Inversion

THE DEATH OF FINAL TAGLESS

THE DEATH OF FINAL TAGLESS

trait Sync[F[_]] extends Bracket[F, Throwable] with Defer[F] {
  def delay[A](thunk: => A): F[A]
}
trait Async[F[_]] extends Sync[F] with LiftIO[F] {
  def async[A](k: (Either[Throwable, A] => Unit) => Unit): F[A]
}
trait Concurrent[F[_]] extends Async[F] {
  def cancelable[A](k: (Either[Throwable, A] => Unit) => CancelToken[F]): F[A]
}
object Ref {
  def of[F[_], A](a: A)(implicit F: Sync[F]): F[Ref[F, A]] = ...
}
object Semaphore
  def apply[F[_]](n: Long)(implicit F: Concurrent[F]): F[Semaphore[F]] = ...
}
final class Stream[F[_], O]{
  //...
  def merge(that: Stream[F, O])(implicit F: Concurrent[F]): Stream[F, O] = ...
  def compile[G[_]](implicit  compiler: Stream.Compiler[F, G]) = ...
  // ...
}
object Compiler{
   implicit def syncInstance[F[_]](implicit F: Sync[F]): Compiler[F, F] = ...
}

Tofu

trait Fire[F[_]] {
  def fireAndForget[A](fa: F[A]): F[Unit]
}

trait Race[F[_]] extends Fire[F] {
  def race[A, B](fa: F[A], fb: F[B]): F[Either[A, B]]
  def never[A]: F[A]
}

trait Start[F[_]] extends Fire[F] with Race[F] {
  def start[A](fa: F[A]): F[Fiber[F, A]]
  def racePair[A, B](fa: F[A], fb: F[B]): F[Either[(A, Fiber[F, B]), (Fiber[F, A], B)]]
}

trait Guarantee[F[_]] {
  def bracket[A, B, C](init: F[A])(action: A => F[B])(release: (A, Boolean) => F[C]): F[B]
}

trait Daemonic[F[_], E] {
  def daemonize[A](process: F[A]): F[Daemon[F, E, A]]
}

Concurrency

trait Raise[F[_], E] {
  def raise[A](err: E): F[A]
}

trait RestoreTo[F[_], G[_]] extends Lift[G, F] {
  def restore[A](fa: F[A]): G[Option[A]]
}
trait Restore[F[_]] extends RestoreTo[F, F] {
  def restoreWith[A](fa: F[A])(ra: => F[A]): F[A]
}

trait HandleTo[F[_], G[_], E] extends RestoreTo[F, G] {
  def handleWith[A](fa: F[A])(f: E => G[A]): G[A]
}

trait Handle[F[_], E] extends HandleTo[F, F, E] with Restore[F] {
  def tryHandleWith[A](fa: F[A])(f: E => Option[F[A]]): F[A]
}

trait ErrorsTo[F[_], G[_], E] extends Raise[F, E] with HandleTo[F, G, E]

trait Errors[F[_], E] extends Raise[F, E] with Handle[F, E] with ErrorsTo[F, F, E] 

Ошибки

trait MakeRef[I[_], F[_]] {
  def refOf[A](a: A): I[Ref[F, A]]
}
trait MakeSemaphore[I[_], F[_]] {
  def semaphore(count: Long): I[Semaphore[F]]
}
trait MakeMVar[I[_], F[_]] {
  def mvarOf[A](a: A): I[MVar[F, A]]
  def mvarEmpty[A]: I[MVar[F, A]]
}
trait MakeDeferred[I[_], F[_]] {
  def deferred[A]: I[Deferred[F, A]]
}


trait MakeAgent[I[_], F[_]] {
  def agentOf[A](a: A): I[Agent[F, A]]
}
trait MakeGatekeeper[I[_], F[_], A] {
  def gatekeeper(available: A): I[Gatekeeper[F, A]]
}
trait MakeQVar[I[_], F[_]] {
  def qvarOf[A](a: A): I[QVar[F, A]]
  def qvarEmpty[A]: I[QVar[F, A]]
}

Фабрики

trait MakeSomething[I[_], F[_]]{
    def make1(arg1: Arg1, arg2: Arg2): I[Something[F]]
    def make2(arg3: Arg3): I[Something[F]]
}

Фабрика

trait Maker[Struct[_[_]], Init[_], Effect[_], Input]{
   def make(input: Input): Init[Struct[F]]
}
sealed trait MVarInput[A]
case class Empty[A]() extends MVarInput[A]
case class Of[A](a: A) extends MVarInput[A]


implicit def makeMVar[A]: Make[MVar[*[_], A, I[_], F[_], MVarInput[A]] 

Иногда

Абстракций

Паттерны

Удобнее

Контекст

Контекст

trait WithContext[F[_], C]{
	def context: F[C]
}

trait WithLocal[F[_], C] extends WithContext[F, C]{
	def local[A](fa: F[A])(project: C => C): F[A]
}

trait WithRun[F[_], G[_], C] extends WithLocal[F, C] with Unlift[G, F] {
	def runContext[A](fa: F[A])(ctx: C): G[A]
}

Среда исполнения

type App[+A] = Env[Environment, A]

@ClasyOptics
case class Config(
  http: HttpConfig,
  db: DBConfig,
  kafka: KafkaConfig,
)

@ClassyOptics
case class Environment(
  @promote config: Config,
  httpClient: HttpClient[App],
  database : DB[App],
  kafka: Kafka[App]
)

WithRun[App, Task, Environment]
WithLocal[App, Environment]
WithContext[App, Environment]

WithLocal[App, Config]
WithLocal[App, HttpClient[App]]

WithLocal[App, DBConfig]
WithLocal[App, HttpConfig]

Embed

def checkSecurity[F[_]: Monad](
    implicit hasUserRepo: WithContext[F, UserRepo[F]], 
             hasSecuriry: WithContext[F, Security[F]]): F[Unit] = 
    for{
    	userRepo <- hasUserRepo.context
        security <- hasSecurity.context
        ....
    }
trait Embed[Module[f[_]]]{
   def embed[F[_]: Monad](fmod: F[Module[F]]): Module[F]
}

Контейнер

@derive(embed)
trait Security[F[_]]{
   def authenticate(login: Login): F[User]
   def checkAuth(user: User, op: Operation): F[Bool]
}

class SecurityContainer[F[_]](fsec: F[Security[F]]) extends Security[F]{
      def authenticate(login: Login) = for{
        security <- fsec
        user     <- security.authenticate(login)
      } yield user
      
      def checkAuth(user: User, op: Operation) = for{
         security <- fsec
         success  <- security.checkAuth(user, op)
      } yield success
}

implicit val embedSecurity[F[_]] = new Embed[Security]{
   def embed[F[_]](fsec: F[Security[F]]) = new SecurityContainer(fsec)
}

implicit val contextualSecurity[F[_]: Monad](implicit has: F WithContext Security[F]): Security[F] =
	implicily[Embed[Security]].embed(has.context)

Зависимости

object DBSecurity{
    def apply[I[_]: Monad, F[_]: Monad :DB :Profile]: I[Security[F]]
}

object DBProfiles{
    def apply[I[_]: Monad, F[_]: Monad: DB: Security]: I[Prodiles[F]]
}

def init: Task[Environment] = for{
   ...
   security <- DBSecurity[Task, App]
   ...
   profiles <- DBProfiles[Task, App]
   ...
}

DI

trait Embed[Module[f[_]]]{
   def embed[F[_]: Monad](fmod: F[Module[F]]): Module[F]
}

=

DI Container

Комбинация

Абстракции высшего порядка

trait FunctorK[A[_[_]]]{
    def mapK[F[_], G[_]](af: A[F])(fk: F ~> G): A[G]
}

trait SemigroupalK[A[_[_]]]{
    def productK[F[_], G[_]](af: A[F], ag: A[G]): A[Tuple2K[F, G, ?]]
}

trait ApplyK[A[_[_]]] extends FunctorK[A] with SemigroupalK[A]{
    def map2K[F[_], G[_], H[_]](af: A[F], ag: A[G])(f: Tuple2K[F, G, ?] ~> H): A[H]
}

Не-функторы

@derive(applyK)
trait Security[F[_]]{
   def authenticate(login: Login): F[User]
   def checkAuth(user: User, op: Operation): F[Bool]
}

type Mid[F[_], A] = F[A] => F[A]

class SecurityLogging[F[_]: Monad: Logging] extends Security[Mid[F, *]]{
   def authenticate(login: Login): F[User] => F[User] = 
   inner => for{
          _ <- debug"started authentication for $login"
          user <- inner
          _ <- debug"finished authentication for $login result $user"
       } yield user 
   def checkAuth(user: User, op: Operation): F[Bool] => F[Bool] = 
   inner => for {
           _      <- debug"checking authorization $op for $user"
           result <- inner
           _      <- debug"finished checking authorization of $op for $user result $result" 
         
       } yield result

}

Аспекты

def attach[F[_]](plugin: Security[Mid[F, *]], impl: Security[F]): Security[F]

def combine[F[_]](s1: Security[Mid[F, *]], s2 : Security[Mid[F, *]]): Security[Mid[F, *]]
val securityPlugins: Security[Mid[App, *]] = 
    securityTracing.when(config.tracing) combine
    securityCaching.when(config.caching) combine
    securityMonitoring.when(config.monitoring)
    
implicit val securiry: Security[App] = securityPlugins.attach(securityImpl)

AOP

Фреймворк?

@derive(representableK)
trait Security[F[_]]{
   def authenticate(login: Login): F[User]
   def checkAuth(user: User, op: Operation): F[Bool]
}

object Security extends ContextEmbed[Security]
trait RepresentableK[Module[f[_]]] extends Embed[Module] with ApplyK[Module]

F

O

P

TF

Спасибо за внимание!