Инвариантный функтор
Формальное определение
Инвариантный функтор поддерживает операцию xmap
(или imap
),
которая преобразует F[A]
в F[B]
с учетом двух функций: A => B
и B => A
.
Если функтор создает новые экземпляры класса типов, добавляя функцию в конец цепочки преобразований, а контравариантный функтор создает их, добавляя функцию в начало цепочки преобразований, то инвариантный функтор создает их с помощью пары двунаправленных преобразований.
Инвариантный функтор должен удовлетворять двум законам:
- Identity (тождественность): Если определен метод идентификации
identity
такой, что:identity(a) == a
, тогдаxmap(ma)(identity, identity) == ma
. - Composition (композиция) -
xmap(xmap(ma, f1, g1), f2, g2) == xmap(ma, f2 compose f1, g1 compose g2)
Также известен как экспоненциальный функтор.
Определение в виде кода на Scala
trait InvariantFunctor[F[_]]:
extension [A](fa: F[A])
def xmap[B](f: A => B, g: B => A): F[B]
Примеры
"Обертка"
import cats.Id
given InvariantFunctor[Id] with
extension [A](fa: Id[A])
override def xmap[B](f: A => B, g: B => A): Id[B] = Id(f(fa))
Докажем, что Id
удовлетворяет законам инвариантного функтора.
-
тождественность:
xmap(ma)(identity, identity) == ma
- по определению метода
xmap
получим:Id(a).xmap(identity, identity) == Id(identity(a)) == Id(a)
- по определению метода
-
композиция:
xmap(xmap(ma, f1, g1), f2, g2) == xmap(ma, f2 compose f1, g1 compose g2)
- по определению метода
xmap
получим:Id(a).xmap(f1, g1).xmap(f2, g2) == Id(f1(a)).xmap(f2, g2) == Id(f2(f1(a)))
иId(a).xmap(f2 compose f1, g1 compose g2) == Id(f2(f1(a)))
- по определению метода
Codec
Зачастую инвариантный функтор используется в кодеках, кодирующих и декодирующих строки.
trait Codec[A]:
def encode(value: A): String
def decode(value: String): A
given InvariantFunctor[Codec] with
extension [A](fa: Codec[A])
override def xmap[B](f: A => B, g: B => A): Codec[B] =
new Codec[B]:
def encode(value: B): String = fa.encode(g(value))
def decode(value: String): B = f(fa.decode(value))
Докажем, что Codec
удовлетворяет законам инвариантного функтора.
-
тождественность:
xmap(ma)(identity, identity) == ma
-
по определению метода
xmap
получим:fa.xmap(identity, identity) ==
def encode(value: A): String = fa.encode(identity(value)) = fa.encode(value) def decode(value: String): A = identity(fa.decode(value)) = = fa.decode(value)
Это эквивалентно исходному
fa
(в случае использования чистых функций) - проксируются его методы.
-
по определению метода
-
композиция:
xmap(xmap(ma, f1, g1), f2, g2) == xmap(ma, f2 compose f1, g1 compose g2)
-
по определению метода
xmap
получимxmap(xmap(ma, f1, g1), f2, g2) ==
:def encode(value: C): String = fb.encode(g2(value)) = fa.encode(g1(g2(value))) def decode(value: String): C = f2(fb.decode(value)) = f2(f1(fa.decode(value)))
xmap(ma, f2 compose f1, g1 compose g2) ==
:def encode(value: C): String = fa.encode(g1 compose g2) = fa.encode(g1(g2(value))) def decode(value: String): C = (f2 compose f1)(fa.decode(value)) = f2(f1(fa.decode(value)))
-
по определению метода
Ссылки: