【问题标题】:Extend NaturalTransformation of Coproducts扩展副产品的自然转化
【发布时间】:2019-04-12 18:36:19
【问题描述】:

我有

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

这就是我卡住的地方。我尝试使用 shapeless 中的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")
        }
  }
}

【问题讨论】:

  • 我只是尝试了一下,然后遇到了似乎是与高级类型相关的隐式解析错误。你能在左边而不是右边建立副产品吗?这将非常简单。
  • 感谢 Travis,是的,可以接受。
  • @TravisBrown 我尝试向左累积,但在实现apply 时未能提供选择器的隐含证据。你介意分享你的尝试吗?谢谢!

标签: scala shapeless scala-cats kind-projector


【解决方案1】:

抱歉,我前几天没来得及发布此内容,但我认为它可以满足您的需求。诀窍是以一种你甚至不需要选择器的方式来安排事情。

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)) 等。)

【讨论】:

  • 感谢 Travis,看起来很棒。明天早上我要试一试!
  • 好的,它按预期工作,非常感谢!只有两个顾虑:向右积累会更自然,能详细说说你遇到了什么问题吗?此外,如果我不注释 or 的返回类型,scalac 会感到困惑并抱怨它的形状不正确。关于如何解决这个问题的任何想法?
  • @GabrielePetronella Here 是我的快速尝试。如果您手动创建实例,它会起作用,但它不会自己找到它们。我可能会搞砸一些简单的事情——我没有花太多时间尝试调试它,实际上我发现上面答案中的版本更自然一些,因为您通常以这种方式构建副产品。
  • @GabrielePetronella 另外我不知道为什么 scalac 不能在这里适当地统一推断的类型,但我并不感到惊讶,它不能。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2022-01-19
  • 2022-07-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-04-24
  • 2021-10-25
相关资源
最近更新 更多