Skip to content

Instantly share code, notes, and snippets.

@Odomontois
Last active September 23, 2020 18:33
Show Gist options
  • Save Odomontois/18503ae41f9b283227dbee727a627468 to your computer and use it in GitHub Desktop.
Save Odomontois/18503ae41f9b283227dbee727a627468 to your computer and use it in GitHub Desktop.
Рак на Миде

Поясняю за Mid по запросу @aleksei_t

Исходная мысль была такая. Итак у нас есть

trait MyBusinessModule[F[_]]{
  def doBusinessThing(entity: Entity, info: Info): F[Value]
  def undoBusinessThing(entity: Entity): F[Respect]
}

Мы привыкли что F - это какой-то там IO - или какой-то трансформер, ридер - шмидер.

Однако ничто в сигнатуре не обязывает нас к такой строгости, мало того, ничего не обязывает нас использовать что-то, что вообще функтор там.

Ну давайте с простого

type Pre[F[_], A] = F[Unit]

это такой странный тип, который вроде и имеет тайп-параметр, но ему всё равно, никакой информации о результате он не несёт

применив MyBusinessModule к Pre[F[_], *] мы получим такой тип

//MyBusinessModule[Pre[F, *]]
{
  def doBusinessThing(entity: Entity, info: Info): F[Unit]
  def undoBusinessThing(entity: Entity): F[Unit]
}

результата мы не производим - только какой-то "эффект", как принято говорить, на основе входных параметров, так можно выразить логирование, проверку входных данных или что-то вроде такого

что, если дальше мы напишем

type Post[F[_], A] = A => F[Unit]

получили и вовсе контравариантный тип по отношению к A. Но что с нашим модулем?

//MyBusinessModule[Post[F, *]]
{
  def doBusinessThing(entity: Entity, info: Info): Value => F[Unit]
  def undoBusinessThing(entity: Entity): Respect => F[Unit]
}

Получили новую сущность, когда результаты уже получены мы можем взять одну или несколько таких штук, чтобы логировать уже результаты, отослать какие-то события о совершённых действия в кафку или бизнес-лог.

И довершает всё следующий тип

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

При наличии Monad[F] мы можем превратить и Pre и Post в такой Mid

Наш модуль, будучи применённым к нему выглядит так

//MyBusinessModule[Mid[F, *]]
{
  def doBusinessThing(entity: Entity, info: Info): F[Value] => F[Value]
  def undoBusinessThing(entity: Entity): F[Respect] => F[Respect]
}

Такой компонент может всё то же, что и предыдущие, но так же может "запустить" F несколько раз, или вообще не запускать.

Т.е. в качестве таких middleware могут выступать кеширование, ретраи и вся остальная техническая или логическая суета, которая не реализуется в вашей инфраструктуре, или требует дополнительного осмысления или специфичного конфига.

Но как теперь использовать такие плагины?

Оказывается, достаточно ApplyK имея

def map2K[F[_], G[_], H[_]](af: A[F], ag: A[G])(f: Tuple2K[F, G, *]~> H]): A[H]

мы как раз и получаем, что если мы можем скомпозить пару "результат основного действия - результат плагина" - мы можем скомпозить и две реализации модуля - основную и подключаемую.

Ну т.е. вызываем этот map2K подставляя F = F, G = Mid[F, *], H = F отдаём наш MyBusinessModule[F] и плагин MyBusinessModule[Mid] в качестве af и ag, осталось только реализовать Tuple2K[F, Mid[F, *], *]~> F

Ну т.е. это примерно реализовать полиморфную функцию [A] (F[A], F[A] => F[A]) => F[A] , реализация очевидно (fa, f) => f(fa)

Т.е. "аппликация" нашего плагина - это просто применение функции, но в результате каждого из методов.

Остальное - сделает макрос, сгенерировавший для вас ApplyK[MyBusinessModule]

Всё это раскидано где-то в исходниках этого модуля, который в принципе был сделан как базовый для core, чтобы макросом можно было выводить RepresentableK для всех остальных штук.

@optician
Copy link

optician commented May 19, 2020

Пример за авторством https://t.me/ppressives

import cats.{Applicative, FlatMap, Monad}
import cats.syntax.semigroup._
import derevo.derive
import derevo.tagless.applyK
import tofu.higherKind.Mid
import tofu.syntax.monadic._

trait Metrics[F[_]] {
  def timed[A](metricsKey: String)(f: F[A]): F[A]
}

trait Logger[F[_]] {
  def info(str: String): F[Unit]
}

@derive(applyK)
trait FooService[F[_]] {
  def foo(a: String): F[Int]
}

object FooService {

  def create[F[_] : Monad](metrics: Metrics[F], logger: Logger[F]): FooService[F] = {
    val mid = (new FooLogging(logger): FooService[Mid[F, *]]) |+| (new FooMetrics(metrics): FooService[Mid[F, *]])
    mid attach new FooImpl[F]
  }

  private final class FooImpl[F[_]: Applicative] extends FooService[F] {
    def foo(a: String): F[Int] = a.length.pure[F]
  }

  private final class FooLogging[F[_]: FlatMap](logger: Logger[F])extends FooService[Mid[F, *]] {
    def foo(a: String): Mid[F, Int] =
      d => logger.info(s"Calling foo with a=$a") *> d.flatTap(res =>logger.info(s"foo returned $res"))
  }

  private final class FooMetrics[F[_]](metrics: Metrics[F]) extends FooService[Mid[F, *]] {
    def foo(a: String): Mid[F, Int] = metrics.timed("timings.foo")(_)
  }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment