Higher Kinded Data

Данные

Типы данных в ФП

  • Произведения
  • Суммы
  • Функции
  • Рекурсия

Произведения в Scala

case class Person(
    firstName: String,
    lastName: Option[String],
    age: Int,
    tags: List[Tag],
)

Использование

val person = 
  Person(
      firstName = "レグ",
      lastName = None,
      age = 214,
      tags = List("robot", "indestructible")
  )

person.firstName // String

person.copy(firstName = newName) // type checking

Внезапная задача

Бестипье

val storedPerson: Map[String, Any] = Map(
    "lastName" -> None,
    "age" -> 18,
    "tags" -> List(),
)

val updatedPerson:  Map[String, Any] = Map(
    "firstName" -> "Nadya", 
    "tags" -> List("starfleet", "viper")
    )

val fullPerson = storedPerson ++ updatedPerson

def recreate(value: Map[String, Any]): Either[String, Person] = ???

Бестипье

val storedPerson: Map[String, Any] = Map(
    "lastName" -> None,
    "age" -> 18,
    "tags" -> List(),
)

val updatedPerson:  Map[String, Any] = Map(
    "firstName" -> "Nadya", 
    "tags" -> List("starfleet", "viper")
    )

val fullPerson = storedPerson ++ updatedPerson

def recreate(value: Map[String, Any]): Either[String, Person] = ???

Поддержка копипасты

case class PartialPerson(
    firstName: Option[String],
    lastName: Option[Option[String]],
    age: Option[Int],
    tags: Option[List[Tag]],
) {
    def toPerson: EitherNel[String, Person] = ???
}

given Monoid[PartialPerson] = ???

given Decoder[PartialPerson] = ???
given Encoder[PartialPerson] = ???
given Swagger[PartialPerson] = ???

Другие дороги боли

  • Reflection (~ String Typing)
  • Shapeless
type PartialPerson = 
    FieldType["firstName" , Option[String]] ::
    FieldType["lastName",  Option[Option[String]]] ::
    FieldType["age", Option[Int]]::
    FieldType["tags", Option[List[Tag]]] ::
    HNil

Ещё задачи

case class PersonVariants(
    firstName: List[String],
    lastName: List[Option[String]],
    age: List[Int],
    tags: List[List[Tag]],
)
case class MutablePerson(
    firstName: Ref[IO, String],
    lastName: Ref[IO, Option[String]],
    age: Ref[IO, Int],
    tags: Ref[IO, List[Tag]],
)
case class PersonChanges(
    firstName: Stream[IO, String],
    lastName: Stream[IO, Option[String]],
    age: Stream[IO, Int],
    tags: Stream[IO, List[Tag]],
)
type Listen[-A] = (A, A) => IO[Unit]
case class PersonListen(
    firstName: Listen[String],
    lastName: Listen[Option[String]],
    age: Listen[Int],
    tags: Listen[List[Tag]],
)

Higher-Kinded Data

case class PersonOf[F[_]](
    firstName: F[String],
    lastName: F[Option[String]],
    age: F[Int],
    tags: F[List[Tag]],
)

Higher-Kinded Data

case class PersonOf[F[_]](
    firstName: F[String],
    lastName: F[Option[String]],
    age: F[Int],
    tags: F[List[Tag]],
)
type Person = PersonOf[Id] // PersonOf[[A] =>> A]
type PartialPerson = PersonOf[Option]
type PersonVariants = PersonOf[List]
type MutablePerson[F[_]] = PersonOf[Ref[F, *]]
type PersonChanges[F[_]] = PersonOf[Stream[F, *]]
type PersonListen[F[_]] = PersonOf[Listen]

Имена

type Name[A] = String

type Names[Data[f[_]]] = Data[Name]

given Names[PersonOf] = PersonOf(
  firstName = "firstName",
  lastName = "lastName",
  age = "age",
  tags = "tags"
)
inline def names[Data[f[_]]](using p: Mirror.ProductOf[Data[Name]]) = 
  p.fromProduct(constValueTuple[p.MirroredElemLabels])

Получение инстансов

type Provided[Data[f[_]], TC[_]] = Data[TC]

given PersonOf[Show] = PersonOf(
  implicitly,
  implicitly,
  implicitly,
  implicitly,
)
inline def provision[P](using p: Mirror.ProductOf[P]): P = 
  p.fromProduct(summonAll[p.MirroredElemTypes])

Functor

trait Functor[F[_]]:
    extension [A, B](fa: F[A])
        def map(f: A => B): F[B]
case class Person[R](
    firstName: R,
    lastName: R,
    age: R,
    tags: R
)

given Functor[Person] with
    extension [A, B](fa: Person[A])
        def map(f: A => B): Person[B] = 
            Person(
                f(fa.firstName),
                f(fa.lastName),
                f(fa.age),
                f(fa.tags),
            )   

Pure / Pointed

trait Pure[F[_]]:
  def pure[A](a: A): F[A] 
case class Person[R](
    firstName: R,
    lastName: R,
    age: R,
    tags: R
)

given Pure[Person] with
    def pure[A](a: A): Person[A] = 
        Person(
                a,
                a,
                a,
                a
            )  

Apply / Semigroupal

trait Apply[F[_]] extends Functor[F]:
  extension [A, B, C](fa: F[A])
    def map2(fb: F[B])(f: (A, B) => C): F[C]
case class Person[R](
    firstName: R,
    lastName: R,
    age: R,
    tags: R
)

given Apply[Person] with
  extension [A, B, C](fa: Person[A])
    def map2(fb: Person[B])(f: (A, B) => C): Person[C] =
        Person(
            f(fa.firstName, fb.firstName),
            f(fa.lastName, fb.lastName),
            f(fa.age, fb.age),
            f(fa.tags, fb.tags)
        )  

Applicative / Monoidal

trait Applicative[F[_]] extends Pure[F] with Apply[F]:
    extension [A, B] (fa: F[A]) override def map (f: A => B): F[B] = 
        fa.map2(unit)((a, _) => f(a))

    def zip[A, B](fa: F[A], fb: F[B]): F[(A, B)] = fa.map2(fb)((_, _))

    extension [A, B, C, D](fa: F[A])
        def map3(fb: F[B], fc: F[C])(f: (A, B, C) => D): F[D] = 
            zip(fa, fb).map2(fc) {
                case ((a, b), c) => f(a, b, c)
            }

    extension [A, B, C, D, E](fa: F[A])
        def map4(fb: F[B], fc: F[C], fd: F[D])(f: (A, B, C, D) => E): F[E] = 
            zip(fa, fb).map2(zip(fc, fd)){ 
                case ((a, b), (c, d)) => f(a, b, c, d) 
            }

Traverse

trait Traverse[T[_]] extends Functor[T]:
  extension [A, B, F[_]: Applicative](ta: T[A])
    def traverse(f: A => F[B]): F[T[B]]

  extension [A, F[+_]: Applicative](ta: T[F[A]])
    def sequence: F[T[A]] = ta.traverse(fa => fa)
given Traverse[Person] with
    extension [A, B, F[_]](ta: Person[A])
        def traverse(f: A => F[B])(using F: Applicative[F]): F[Person[B]] = 
            f(ta.firstName).map4(
                f(ta.lastName),
                f(ta.age),
                f(ta.tags)
            )(Person(_, _, _, _))

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

trait FunctionK[F[_], G[_]]:
    def apply[A](fa: F[A]): G[A]
    
type FunctionK[F[_], G[_]] = [A] => F[A] => G[A]

FunctorK

trait FunctorK[U[f[_]]]:
  extension [F[_], G[_]] (obj: U[F]) 
    def mapK (f: [A] => F[A] => G[A]): U[G]
case class PersonOf[F[_]](
    firstName: F[String],
    lastName: F[Option[String]],
    age: F[Int],
    tags: F[List[Tag]],
)

given FunctorK[PersonOf] with
  extension [F[_], G[_]] (p: PersonOf[F]) 
    def mapK (f: [A] => F[A] => G[A]): PersonOf[G] = 
        PersonOf(
          f(p.firstName),// F[String] -> G[String]
          f(p.lastName), // F[Option[String]]->G[Option[String]]
          f(p.age),      // F[Int] -> G[Int]
          f(p.tags),     // F[List[Tag]] -> G[List[Tag]]
        )

PureK / PointedK

trait PureK[U[f[_]]] extends FunctorK[U]:
  def pureK[F[_]](gen: [A] => () => F[A]): U[F]
case class PersonOf[F[_]](
    firstName: F[String],
    lastName: F[Option[String]],
    age: F[Int],
    tags: F[List[Tag]],
)

given PureK[PersonOf] with
  def pureK[F[_]](gen: [A] => () => F[A]): PersonOf[F] = 
    PersonOf(
      gen(), // F[String]
      gen(), // F[Option[String]]
      gen(), // F[Int]
      gen(), // F[List[Tag]]
    )

ApplyK / SemigroupalK

trait ApplyK[U[f[_]]] extends FunctorK[U]:
  extension [F[_], G[_], H[_]] (left: U[F])
    def map2K(right: U[G])(f: [A] => (F[A], G[A]) => H[A]): U[H]
    
case class PersonOf[F[_]](
    firstName: F[String],
    lastName: F[Option[String]],
    age: F[Int],
    tags: F[List[Tag]],
)
given ApplyK[PersonOf] with
  extension [F[_], G[_], H[_]] (left: PersonOf[F])
    def map2K(right: PersonOf[G])
    (f: [A] => (F[A], G[A]) => H[A]): PersonOf[H] = 
      PersonOf(
        f(left.firstName, right.firstName), 
         //  F[String]        G[String]    => H[String]
        f(left.lastName, right.lastName),  // H[Option[String]]
        f(left.age, right.age),            // H[Int]
        f(left.tags, right.tags)           // H[List[Tag]]
      )

ApplicativeK / MonoidalK

trait ApplicativeK[U[f[_]]] extends ApplyK[U] with PureK[U]:  
  extension [F[_], G[_]] (obj: U[F])
    override def mapK(f: [A] => F[A] => G[A]): U[G] = 
      obj.map2K[F, [A] =>> Unit, G](
        pureK([A] => () => ()))(
          [A] => (x: F[A], y: Unit) => f(x))  
  
  extension [F[_], G[_], H[_]] (left: U[F])
    def map2Given(right: U[G])(
         f: [A] => (F[A]) => G[A] ?=> H[A]): U[H] = 
      left.map2K(right)([A] => (fa: F[A], ga: G[A]) => {
          given G[A] = ga
          f(fa)        
      })

TraverseK

trait TraverseK[U[f[_]]] extends FunctorK[U]:
  extension[F[_], G[+_], H[_]](uf: U[F])
    def traverseK(f: [A] => F[A] => G[H[A]])(
      using Applicative[G]): G[U[H]]
given TraverseK[PersonOf] with
  extension[F[_], G[+_], H[_]](uf: PersonOf[F])
    def traverseK(f: [A] => F[A] => G[H[A]])(
      using Applicative[G]): G[PersonOf[H]] = 
        f(uf.firstName).map4(
          f(uf.lastName),
          f(uf.age),
          f(uf.tags)
        )(PersonOf(_, _, _, _))

HKD

type Rep[-U[f[_]], A] = [F[_]] => U[F] => F[A]

trait Craft[U[f[_]]] 
    extends RepresentableK[U] 
    with TraverseK[U]:
  def craft[F[+_]: Applicative, G[_]](
  	gain: [A] => Rep[U, A] => F[G[A]]
    ): F[U[G]]
    
object Craft:
  inline def derived[U[f[_]] <: Product]
    : Craft[U] =  deriveCraft  
    
case class PersonOf[F[_]](...) 
  derives Craft

Авто Моноиды

given [Data[f[_]]](using HKD: Craft[Data]): Monoid[Data[Option]] with
  val empty = HKD.pureK([A] => () => None)

  def combine(x: Data[Option], y: Data[Option]): Data[Option] = 
      x.map2K(y)([A] => (xo: Option[A], yo: Option[A]) => yo.orElse(xo))

Комбинация и обход

extension [Data[f[_]]: Craft] (xs: Data[Option])(using names: Data[Name]) 
    def build: EitherNel[String, Data[[A] =>> A]] = 
        names.map2K[H = [A] =>> EitherNel[String, A]](xs)(
            [A] => (name: String, x: Option[A]) => 
                x.toRightNel(name)
        ).parSequenceId
trait TraversableK[U[f[_]]] extends FunctorK[U]:
	extension[F[_]](uf: U[F])  
   		def parSequenceId(using Parallel[F]): F[U[Id]] = ...

Пример

@main def personParts = 
    val init = Monoid.empty[PersonOf[Option]]
    
    val p1 = init.copy(
        firstName = Some("Oleg"),
        lastName = Some(None),
    )
    val p2 = init.copy(
        age = Some(35),
        tags = Some(List("scala", "love"))
    )

    println(init.build)
    println(p1.build)
    println(p2.build)
    println((p1 |+| p2).build)
[info] running hkd.personParts 
Left(NonEmptyList(firstName, lastName, age, tags))
Left(NonEmptyList(age, tags))
Left(NonEmptyList(firstName, lastName))
Right(PersonOf(Oleg,None,35,List(scala, love)))

Извлечение коллекции

opaque type Const[X, +A] = X

object Const:
    def apply[X](x: X): Const[X, Nothing] = x

    given [X: Monoid] : Applicative[[a] =>> Const[X, a]] with
        def pure[A](x: A) = Monoid.empty[X]
        extension [A, B] (xa: Const[X, A])
            override def map(f: A => B): Const[X, B] = xa
        extension [A, B, C](xa: Const[X, A])
            def map2(xb: Const[X, B])(f: (A, B) => C) = xa |+| xb

    extension [A] (x: Const[A, Any]) def value: A = x
    
def collectVector[G[+_], Data[f[_]]](xs: Data[G])
  (using TraversableK[Data]): Vector[G[Any]] = 
    xs.traverseK(
    	[A] => (x: G[A]) => Const[Vector[G[Any]]](Vector(x))
    ).value

Quantified Constraints

trait ShowK[F[_]]:
    given showK[A: Show]: Show[F[A]]

object ShowK:
    given ShowK[Identity] with 
        def showK[A: Show] = summon
    
    given ShowK[Option] with
        def showK[A: Show] = implicitly

    given [A: Show] : ShowK[[B] =>> Either[A, B]] with
        def showK[B: Show] = implicitly

trait EqK[F[_]]:
    given eqK[A: Eq]: Eq[F[A]]

trait EncoderK[F[_]]:
    given encoderK[A: Encoder]: Encoder[F[A]]
   
trait ConstraintK[F[_], TC[_]]:
    given instance[A : TC]: TC[F[A]]

Show

given [Data[f[_]] <: Product: Craft, F[_]]( using
    names: Data[Name], 
    shows: Data[Show],
    showK: ShowK[F]): Show[Data[F]] with
    
    def show(xs: Data[F]): String = 
        import showK.given
        val fields: Data[[A] =>> String] = 
            xs.map2Given(shows)(
            	[A] => (fa: F[A]) => (x: Show[A]) ?=> fa.show)
                
        val prefix = xs.productPrefix
        
        val pairs: Data[[A] =>> String] = 
            names.map2K(fields)(
            	[A] => (name: String, fld: String) => s"$name: $fld")
                
        hkdToVector(pairs).mkString(s"$prefix{", ",", "}")

Что в итоге писать-то надо

case class PersonOf[F[_]](
    firstName: F[String],
    lastName: F[Option[String]],
    age: F[Int],
    tags: F[List[Tag]],
) derives Craft

given PersonOf[Name] = names[PersonOf]
given PersonOf[Show] = provision[PersonOf[Show]] 

@main def personMain =
    val person = PersonOf[Id](
        "Oleg",
        Some("Nizhnik"),
        36,
        List("scala", "love")
    )
    println(person.show)
[info] running personMain 
PersonOf{firstName: Oleg,lastName: Some(Nizhnik),age: 36,tags: List(scala, love)}

HKD

  • Неограниченные специализированные формы данных

  • Коллекции с именованными элементами

  • Типобезопасный вывод инстансов

Обходные пути /

Помимо произведений

Рекурсия

case class Person[F[_]]( 
  ... , 
  children F[List[Person[Id]]],
  workplace: F[Option[Workplace[Id]]],
  tastes: F[Genre => Double],
 )

Суммы \ ADT \ Enum

stay tuned....

Вопросы?