[小ネタ] Functorの合成によるBiFunctorの構成

Category Theory for Programmersで紹介されていた既存のbifunctorと2つのfunctorからbifunctorを構成するサンプルをScalaで実装してみました。
2022.07.07

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

[小ネタ] Functorの合成によるBiFunctorの構成

はじめに

前回に引き続きCategory Theory for Programmersで紹介されていた既存のbifunctorと2つのfunctorからbifunctorを構成するサンプルをScalaで実装してみました。

CTPでの実装

-- bfはbifunctor, fuおよびguはfunctor
new type BiComp bf fu gu a b = BiComp (bf (fu a) (gu b))

instance (BiFunctor bu, Functor fu, Functor gu) =>
	BiFunctor (BiComp bu fu gu) where
		bimap f1 f2 (BiComp x) = BiComp (bimap (fmap f1) (fmap f2) x)

Scalaでの実装

package example

import cats.data.Const
import cats.implicits._
import cats.laws.discipline.BifunctorTests
import cats.{Bifunctor, Eq, Functor}
import org.scalatest.funsuite.AnyFunSuiteLike
import org.typelevel.discipline.scalatest.FunSuiteDiscipline
import org.scalacheck.ScalacheckShapeless._

class BiCompTest
    extends AnyFunSuiteLike
    with org.scalatest.prop.Configuration
    with FunSuiteDiscipline {

 
  final case class BiComp[BF[_, _]: Bifunctor, FU[_]: Functor, GU[
      _
  ]: Functor, A, B](
      bf: BF[FU[A], GU[B]]
  )

  object BiComp {
    implicit def eq[BU[_, _], FU[_], GU[_], A: Eq, B: Eq]
        : Eq[BiComp[BU, FU, GU, A, B]] = Eq.allEqual
    implicit def bf[BF[_, _]: Bifunctor, FU[_]: Functor, GU[_]: Functor]
        : Bifunctor[BiComp[BF, FU, GU, *, *]] =
      new Bifunctor[BiComp[BF, FU, GU, *, *]] {
        override def bimap[A, B, C, D](
            fab: BiComp[BF, FU, GU, A, B]
        )(f: A => C, g: B => D): BiComp[BF, FU, GU, C, D] =
          BiComp(
            Bifunctor[BF].bimap(fab.bf)(
              Functor[FU].fmap(_)(f),
              Functor[GU].fmap(_)(g)
            )
          )
      }
  }
}

CTP中で言及されている内容にそってConst, Identity, Sum type(coproduct), Either( product) からなるBifunctorを構成してlaw tesingしてみます。

 final case class Identity[A](a: A)
  object Identity {
    implicit def eq[A: Eq]: Eq[Identity[A]] = Eq.allEqual
    implicit val identityFunctor: Functor[Identity] = new Functor[Identity] {
      override def map[A, B](fa: Identity[A])(f: A => B): Identity[B] =
        Identity(f(fa.a))
    }
  }

  import BiComp._
  import Identity._

  // Coproduct
  type SumBF[A, B] = BiComp[Either, Const[Unit, *], Identity, A, B]
  // Product
  type ProductBF[A, B] = BiComp[Tuple2, Option, List, A, B]

  // Law testing
  checkAll(
    "SumBF.BiFunctorTests",
    BifunctorTests[SumBF]
      .bifunctor[String, Long, Int, String, Long, Int]
  )

  checkAll(
    "ProductBF.BiFunctorTests",
    BifunctorTests[ProductBF]
      .bifunctor[String, Long, Int, String, Long, Int]
  )
/*
[info] BiCompTest:
[info] - SumBF.BiFunctorTests.Bifunctor.Bifunctor Identity
[info] - SumBF.BiFunctorTests.Bifunctor.Bifunctor associativity
[info] - SumBF.BiFunctorTests.Bifunctor.Bifunctor leftMap Identity
[info] - SumBF.BiFunctorTests.Bifunctor.Bifunctor leftMap associativity
[info] - ProductBF.BiFunctorTests.Bifunctor.Bifunctor Identity
[info] - ProductBF.BiFunctorTests.Bifunctor.Bifunctor associativity
[info] - ProductBF.BiFunctorTests.Bifunctor.Bifunctor leftMap Identity
[info] - ProductBF.BiFunctorTests.Bifunctor.Bifunctor leftMap associativity
*/

まとめ

Scalaだと意外と実装するものが多くて時間がかかりました。