Type Classes

def addToAll(
    list: List[Int],
    value: Int
): List[Int] =
  list.map(x => x + value)

def addToAll(
    list: List[String],
    value: String
): List[String] =
  list.map(x => x + value)
def addToAll[A](
    list: List[A],
    value: A
): List[A] =
  list.map(x => x + value)

<console>:11: error: type mismatch;
 found   : A
 required: String
trait Addable {
  def add(other: ???)
}
trait Addable[A] {
  def add(other: A): A
}

def addToAll[A <: Addable[A]](
    list: List[A],
    value: A
): List[A] =
  list.map(x => x.add(value))
case class Vector2D(
    x: Int,
    y: Int
) extends Addable[Vector2D] {
  override def add(other: Vector2D) =
    Vector2D(
      this.x + other.x,
      this.y + other.y
    )
}
scala> addToAll(
  List(
    Vector2D(3, 4),
    Vector2D(5, 6)
  ),
  Vector2D(1, 2)
)

res1: List[Vector2D] =
  List(Vector2D(4,6), Vector2D(6,8))
trait Addable[A] {
  def add(first: A, second: A): A
}
case class Vector2D(x: Int, y: Int)

val vector2DAddable: Addable[Vector2D] =
  new Addable[Vector2D] {
    override def add(
        first: Vector2D,
        second: Vector2D
    ): Vector2D =
      Vector2D(
        first.x + second.x,
        first.y + second.y
      )
  }
def addToAll[A](
    list: List[A],
    value: A,
    addable: Addable[A]
): List[A] =
  list.map(x => addable.add(x, value))
scala> addToAll(
  List(
    Vector2D(3, 4),
    Vector2D(5, 6)
  ),
  Vector2D(1, 2),
  vector2Daddable
)

res2: List[Vector2D] =
  List(Vector2D(4,6), Vector2D(6,8))
val intAddable: Addable[Int] =
  new Addable[Int] {
    override def add(
        first: Int,
        second: Int
    ): Int =
      first + second
  }

scala> addToAll(
  List(1, 2), 3, intAddable
)
res3: List[Int] = List(4, 5)
def addToAll[A](
    list: List[A],
    value: A
)(
    implicit addable: Addable[A]
): List[A] =
  list.map(x => addable.add(x, value))
implicit val intAddable: Addable[Int] =
  ...

scala> addToAll(List(1, 2), 3)
res4: List[Int] = List(4, 5)

scala> addToAll(
  List(1, 2), 3
)(intAddable)
res5: List[Int] = List(4, 5)

Type Classes

trait Addable[A] {
  def add(first: A, second: A): A
}
  • Класс типов
  • Экземпляры класса типов
  • Интерфейсы с использованием неявных параметров
  • Интерфейсы с использованием обогащающих классов и неявных параметров
  • intAddable.add(0, 1)

    vectorAddable.add(
      Vector2D(0, 1),
      Vector2D(2, 3)
    )

    Интерфейсы с неявными параметрами

    object Addable {
      def add[A](
          first: A,
          second: A
      )(
          implicit addable: Addable[A]
      ): A =
        addable.add(first, second)
    }
    Addable.add(0, 1)
    Addable.add(
      Vector2D(0, 1),
      Vector2D(2, 3)
    )

    def addToAll[A: Addable](
        list: List[A],
        value: A
    ): List[A] =
      list.map(x => Addable.add(x, value))

    Обогащающие классы

    implicit class AddableOps[A](value: A) {
      def +(
          other: A
      )(
          implicit addable: Addable[A]
      ): A =
        addable.add(value, other)
    }
    scala> Vector2D(1, 2) + Vector2D(3, 4)
    res1: Vector2D = Vector2D(4,6)

    def addToAll[A: Addable](
        list: List[A],
        value: A
    ): List[A] =
      list.map(x => x + value)

    Конструкторы типов

    implicit def lAdd[A]: Addable[List[A]] =
      new Addable[List[A]] {
        override def add(
            first: List[A],
            second: List[A]
        ): List[A] =
          first ++ second
      }

    scala> List(1, 2) + List(3, 4, 5)
    res0: List[Int] = List(1, 2, 3, 4, 5)
    implicit def oa[A : Addable]
      : Addable[Option[A]] =
      new Addable[Option[A]] {
        override def add(
            first: Option[A],
            second: Option[A]
        ): Option[A] =
          first.fold(second){ x =>
            second.fold(first){ y =>
              Some(x + y)
            }
          }
      }
    scala> Option(3) + Option(5)
    res1: Option[Int] = Some(8)

    Отношения типов

    trait Convertible[A, B] {
      def convert(value: A) : B
    }

    object Convertible {
      def convert[A, B](
          value: A
        )(
          implicit cv: Convertible[A, B]
      ): B =
        convertible.convert(value)
    }
    implicit def optionListCV[A]
      : Convertible[Option[A], List[A]] =
      new Convertible[Option[A], List[A]] {
        override def convert(
            value: Option[A]
        ): List[A] =
          value.fold(List.empty[A])(List(_))   }

    scala> Convertible.convert(Option(5))
    res0: List[Int] = List(5)

    Вариантность типовых параметров

    scala> Convertible.convert(Some(3))

    <console>:15: error: could not find implicit value for parameter convertible: Convertible[Some[Int],B]
    trait Convertible[-A, +B] {
      def convert(value: A): B
    }

    scala> Convertible.convert(Some(3))
    res0: List[Int] = List(3)

    Реализация методов

    trait Eq[A] {

      def eqv(a: A, b: A): Boolean =
        !neqv(a, b)

      def neqv(a: A, b: A): Boolean =
        !eqv(a, b)

    }
    implicit val intEq: Eq[Int] =
      new Eq[Int] {
        override def eqv(
            a: Int,
            b: Int
        ): Boolean =
          first == second
    }

    scala> intEq.neqv(0, 1)
    res1: Boolean = true

    Наследование

    trait Ordered[A] extends Eq[A] {

      def order(first: A, second: A): Int

      override def eqv(
          first: A,
          second: A
      ): Boolean =
        order(first, second) == 0

    }

    Классы типов для конструкторов типов

    trait Functor[F[_]]{
      def fmap[A, B](
          value : F[A],
          func: A => B
      ): F[B]
    }
    implicit val lFunc: Functor[List] =
      new Functor[List] {
        override def fmap[A, B](
            value: List[A],
            func: A => B
        ): List[B] =
          value.map(func)
      }

    scala> def add1(x: Int) = x + 1
    scala> lFunc.fmap(List(1, 2), add1)
    res1: List[Int] = List(2, 3)

    Автоматическая генерация экземпляров

    case class A(a: Int, b: String)

    case class B(a: A, b: List[Int])

    import io.circe.generic.semiauto._ implicit val aEncoder: Encoder[A] =
      deriveEncoder[A]
    implicit val bEncoder: Encoder[B] =
      deriveEncoder[B]
    scala> bEncoder(
      B(A(1, "a"), List(3, 2))
    ).spaces2

    res0: String =
    {
      "a" : {
        "a" : 1,
        "b" : "a"
      },
      "b" : [3, 2]
    }