推广副产物的自然变换



我有

F ~> H
G ~> H

式中~>cats.NaturalTransformation

我可以构造一个
λ[A => F[A] :+: G[A] :+: CNil] ~> H

使用kind-projector语法提高可读性

我是这样做的

def or[G[_]](g: G ~> H): λ[A => F[A] :+: G[A] :+: CNil] ~> H =
  new (λ[A => F[A] :+: G[A] :+: CNil] ~> H) {
    def apply[A](fa: F[A] :+: G[A] :+: CNil): H[A] =
      (fa.select[F[A]], fa.select[G[A]]) match {
        case (Some(ff), None) => f(ff)
        case (None, Some(gg)) => g(gg)
        // this can't happen, due to the definition of Coproduct
        case _ => throw new Exception("Something is wrong")
  }
}

这是可行的,尽管我愿意听取建议,因为它看起来不太漂亮。

如果我有

λ[A => F[A] :+: G[A] :+: CNil] ~> H
K ~> H
我也应该能够构造一个
λ[A => F[A] :+: G[A] :+: K[A] :+: CNil] ~> H

这就是我被困住的地方。我尝试使用ExtendRight从无形,但我不能得到它的工作。这是我的尝试:

def or[F[_] <: Coproduct, G[_], H[_], FG[_] <: Coproduct](f: F ~> H, g: G ~> H)(
  implicit e: ExtendRight.Aux[F[_], G[_], FG[_]]
): FG ~> H = new (FG ~> H) {
  def apply[A](fg: FG[A])(implicit
    sf: Selector[FG[A], F[A]],
    sg: Selector[FG[A], G[A]]
  ): H[A] =
    (fg.select[F[A]], fg.select[G[A]]) match {
      case (Some(ff), None) => f(ff)
      case (None, Some(gg)) => g(gg)
      // this can't happen, due to the definition of Coproduct
      case _ => throw new Exception("Something is wrong")
    }

}

但是编译器找不到ExtendRight参数的隐式证据。

这里有一个可以玩的MWE

import shapeless._
import shapeless.ops.coproduct._
import cats.~>
object Bar {
  val optionToList = new (Option ~> List) {
    def apply[A](x: Option[A]): List[A] = x match {
      case None => Nil
      case Some(a) => List(a)
    }
  }
  val idToList = new (Id ~> List) {
    def apply[A](x: Id[A]): List[A] = List(x)
  }
  val tryToList = new (scala.util.Try ~> List) {
    def apply[A](x: scala.util.Try[A]): List[A] = x match {
      case scala.util.Failure(_) => Nil
      case scala.util.Success(a) => List(a)
    }
  }
  type OI[A] = Option[A] :+: Id[A] :+: CNil
  val optionAndId: OI ~> List = Foo.or(optionToList, idToList)
  val all = Foo.or2(optionAndId, tryToList)
}
object Foo {
  def or[F[_], G[_], H[_]](f: F ~> H, g: G ~> H): λ[A => F[A] :+: G[A] :+: CNil] ~> H =
    new (λ[A => F[A] :+: G[A] :+: CNil] ~> H) {
      def apply[A](fa: F[A] :+: G[A] :+: CNil): H[A] =
        (fa.select[F[A]], fa.select[G[A]]) match {
          case (Some(ff), None) => f(ff)
          case (None, Some(gg)) => g(gg)
          // this can't happen, due to the definition of Coproduct
          case _ => throw new Exception("Something is wrong, most likely in the type system")
        }
    }
  def or2[F[_] <: Coproduct, G[_], H[_], FG[_] <: Coproduct](f: F ~> H, g: G ~> H)(implicit
    e: ExtendRight.Aux[F[_], G[_], FG[_]]
    ): FG ~> H = new (FG ~> H) {
      def apply[A](fg: FG[A])(implicit
        sf: Selector[FG[A], F[A]],
        sg: Selector[FG[A], G[A]]
      ): H[A] =
        (fg.select[F[A]], fg.select[G[A]]) match {
          case (Some(ff), None) => f(ff)
          case (None, Some(gg)) => g(gg)
          // this can't happen, due to the definition of Coproduct
          case _ => throw new Exception("Something is wrong, most likely in the type system")
        }
  }
}

对不起,我没有抽出时间来张贴这几天,但我认为它是你想要的。这里的技巧是用一种你甚至不需要选择器的方式来安排事情。

import shapeless._
import shapeless.ops.coproduct._
import cats.~>
def or[F[_], G[_], H[_]](
  f: F ~> H,
  g: G ~> H
): ({ type L[x] = F[x] :+: G[x] :+: CNil })#L ~> H =
  new (({ type L[x] = F[x] :+: G[x] :+: CNil })#L ~> H) {
    object fg extends Poly1 {
      implicit def atF[A]: Case.Aux[F[A], H[A]] = at(f(_))
      implicit def atG[A]: Case.Aux[G[A], H[A]] = at(g(_))
    }
    def apply[A](c: F[A] :+: G[A] :+: CNil): H[A] = c.fold(fg)
  }
def or2[F[_], G[_] <: Coproduct, H[_]](
  f: F ~> H,
  g: G ~> H
): ({ type L[x] = F[x] :+: G[x] })#L ~> H =
  new (({ type L[x] = F[x] :+: G[x] })#L ~> H) {
    def apply[A](c: F[A] :+: G[A]): H[A] = c match {
      case Inl(fa) => f(fa)
      case Inr(ga) => g(ga)
    }
  }

(请注意,我在or中使用Poly1,以避免需要处理CNil的异常情况。)

现在你可以这样写:

val optionToList = new (Option ~> List) {
  def apply[A](x: Option[A]): List[A] = x.fold[List[A]](Nil)(List(_))
}
val idToList = new (Id ~> List) {
  def apply[A](x: Id[A]): List[A] = List(x)
}
val tryToList = new (scala.util.Try ~> List) {
  def apply[A](x: scala.util.Try[A]): List[A] = x match {
    case scala.util.Failure(_) => Nil
    case scala.util.Success(a) => List(a)
  }
}

然后:

scala> type OI[A] = Option[A] :+: Id[A] :+: CNil
defined type alias OI
scala> val optionAndId: OI ~> List = or(optionToList, idToList)
optionAndId: cats.~>[OI,List] = $anon$1@55224c4a
scala> val all = or2(tryToList, optionAndId)
all: cats.~>[[x]shapeless.:+:[scala.util.Try[x],OI[x]],List] = $anon$2@536a993
scala> all(Inl(scala.util.Try('foo)))
res8: List[Symbol] = List('foo)
scala> all(Inr(Inl(Option('foo))))
res9: List[Symbol] = List('foo)
scala> all(Inr(Inr(Inl('foo))))
res10: List[Symbol] = List('foo)

(如果你不介意写出类型,当然也可以使用Coproduct[...](Option('foo))等)

最新更新