Skip to content

Instantly share code, notes, and snippets.

@kailuowang
Last active January 2, 2022 02:41
Show Gist options
  • Save kailuowang/7377633fd8cf691834eb2130afe8c129 to your computer and use it in GitHub Desktop.
Save kailuowang/7377633fd8cf691834eb2130afe8c129 to your computer and use it in GitHub Desktop.
trait IsoK[F[_], G[_]] { self =>
def from[A](fa: F[A]): G[A]
def to[A](ga: G[A]): F[A]
def flip: IsoK[G, F] = new IsoK[G, F] {
def from[A](ga: G[A]): F[A] = self.to(ga)
def to[A](fa: F[A]): G[A] = self.from(fa)
}
def compose[H[_]](that: IsoK[G, H]): IsoK[F, H] = new IsoK[F, H]{
def from[A](fa: F[A]): H[A] = that.from(self.from(fa))
def to[A](ha: H[A]): F[A] = self.to(that.to(ha))
}
//convenient shortcut method
implicit def monad(implicit F: Monad[F]): Monad[G] = IsoK.monad[F, G](F, this)
implicit def applicative(implicit F: Applicative[F]): Applicative[G] = IsoK.applicative[F, G](F, this)
}
object IsoK extends IsoInstances with IsoProvidedInstances {
def apply[F[_], G[_]](implicit inst: IsoK[F, G]): IsoK[F, G] = inst
}
trait IsoInstances {
implicit def optionT[F[_]]: IsoK[OptionT[F, *], Lambda[A => F[Option[A]]]] = new IsoK[OptionT[F, *], Lambda[A => F[Option[A]]]] {
def from[A](fa: OptionT[F, A]): F[Option[A]] = fa.value
def to[A](ga: F[Option[A]]): OptionT[F, A] = OptionT(ga)
}
}
sealed private[cats] trait IsoProvidedInstances {
def applicative[F[_], G[_]](implicit F: Applicative[F], I: IsoK[F, G]): Applicative[G] =
new Applicative[G] {
import I._
def pure[A](r: A): G[A] = from(F.pure(r))
override def map[A, B](ga: G[A])(f: A => B): G[B] =
from(F.map(to(ga))(f))
override def map2[A, B, C](ga: G[A], gb: G[B])(fn: (A, B) => C): G[C] =
from(F.map2(to(ga), to(gb))(fn))
override def product[A, B](ga: G[A], gb: G[B]): G[(A, B)] =
from(F.product(to(ga), to(gb)))
def ap[A, B](f: G[A => B])(ga: G[A]): G[B] =
from(F.ap(to(f))(to(ga)))
}
def monad[F[_], G[_]](implicit F: Monad[F], I: IsoK[F, G]): Monad[G] =
new Monad[G] {
import I._
def pure[A](r: A): G[A] = from(F.pure(r))
def flatMap[A, B](ga: G[A])(f: A => G[B]): G[B] =
from(F.flatMap(to(ga))(f.map(to)))
override def map[A, B](ga: G[A])(f: A => B): G[B] =
from(F.map(to(ga))(f))
override def map2[A, B, C](ga: G[A], gb: G[B])(fn: (A, B) => C): G[C] =
from(F.map2(to(ga), to(gb))(fn))
override def product[A, B](ga: G[A], gb: G[B]): G[(A, B)] =
from(F.product(to(ga), to(gb)))
override def ap[A, B](f: G[A => B])(ga: G[A]): G[B] =
from(F.ap(to(f))(to(ga)))
def tailRecM[A, B](a: A)(fn: A => G[Either[A, B]]): G[B] =
from(F.tailRecM(a)(fn.map(to)))
}
def distributive[F[_], G[_]](implicit F: Distributive[F], I: IsoK[F, G]): Distributive[G] =
new Distributive[G] {
import I._
def map[A, B](ga: G[A])(f: A => B): G[B] = from(F.map(to(ga))(f))
def distribute[H[_]: Functor, A, B](ha: H[A])(f: A => G[B]): G[H[B]] =
from(F.distribute(ha)(f.map(to)))
}
}
@kailuowang
Copy link
Author

kailuowang commented Jan 2, 2022

Related issue
typelevel/cats#1610 by @ceedubs

Related Gitter conversation: https://gitter.im/typelevel/cats?at=58f5f7e801d449152eb5c5dc

It may be nice to provide an easy way to create type class instances that simply delegate to the type class instances of an isomorphic type. For example, if I create final case class DecodeJson[A](decode: Kleisli[Either[String, ?], Json, A]), then I can easily create an iso functor (a bidirectional tranformation via a pair of DecodeJson ~> Kleisli[Either[String, ?], Json, ?] and Kleisli[Either[String, ?], Json, ?] ~> DecodeJson). It seems like I should be able to pass in this iso functor to create a Monad[DecodeJson] that simply delegates through to the Kleisli Monad and runs the transformations on both ends.

I mentioned this use-case as motivation for #992, but that ticket seems to be stalled, and I'm not sure whether we'll go through with it. I think that this functionality could be useful whether or not anything happens with #992.

@kailuowang
Copy link
Author

https://github.com/http4s/http4s/blob/main/core/shared/src/main/scala/org/http4s/FormDataDecoder.scala#L239

Instance of FormDAtaDecoder in Http4s can be written as follows

  import cats._
  import cats.data._
  import org.http4s.ParseFailure
  sealed trait FormDataDecoder[A] {
    def apply(data: Map[String, Chain[String]]): ValidatedNel[ParseFailure, A]
  }
  object FormDataDecoder {
    type NestedFDD[A] = Nested[(Map[String, Chain[String]]) => *, ValidatedNel[ParseFailure, *], A]
    val isoKNested = new IsoK[NestedFDD, FormDataDecoder] {
      override def from[A](fa: NestedFDD[A]): FormDataDecoder[A] = new FormDataDecoder[A] {
        def apply(data: Map[String, Chain[String]]): ValidatedNel[ParseFailure, A] =
          fa.value(data)
      }

      override def to[A](ga: FormDataDecoder[A]): NestedFDD[A] = Nested(ga.apply)
    }
    implicit val applicativeInst = isoKNested.applicative
 }

@kailuowang
Copy link
Author

Basically this allows you to easily take advantage of the Monad[Function1] if you alg trait is basically a A=>B or Transformer such as OptionT or Nested, if your alg trait is A => T[B]

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