【问题标题】:Scala implicit for arbitrarily deep Functor compositionScala 隐式用于任意深度的 Functor 组合
【发布时间】:2014-11-09 22:48:43
【问题描述】:

我正在尝试为 Scala 中的现有类 Elem 提供扩展方法。但是,我还希望任何M[Elem] 都可以使用这些操作,只要M 的Scalaz Functor 在范围内。行为始终是使用map 将操作应用于函子。

import scalaz._
import Scalaz._

class Elem

implicit class Ops[F[_]: Functor, A <% Elem](self: F[A]) {
  def foo = self.map(_ => "bar")
}

val elem = new Elem

// TODO (nice to have): can we avoid this "explicit implicit" conversion?
implicit def idOps[A <% Elem](self: A) = new Ops[Id, A](self)

elem.foo                        // bar
Option(elem).foo                // Some(bar)
List(elem).foo                  // List(bar)

我想更进一步,让我的扩展方法可用于任意深度的函子,例如List[Option[Elem]]Option[Option[Option[Elem]]]。我能够为两个函子的组合编写一个隐式提供 Ops,但我无法将其推广到任意嵌套深度:

// TODO: can we improve this to provide arbitrarily deep functor composition?
implicit def compositeOps[F[_]: Functor, G[_]: Functor, A <% Elem](self: F[G[A]]) = {
  implicit val FG = implicitly[Functor[F]].compose[G]
  new Ops[({ type FG[X] = F[G[X]] })#FG, A](self)
}

List(Option(elem)).foo          // List(Some(bar))
Option(List(Option(elem))).foo  // doesn't compile

有什么办法可以做到吗?

【问题讨论】:

    标签: scala scalaz


    【解决方案1】:

    您可以递归地使隐式助手可用:

    sealed trait Helper[FA] {
      type A
      type F[_]
      def w(fa: FA): F[A]
      val f: Functor[F]
    }
    trait Helper1 {
      implicit def nil[A] = new Helper[A] {
        type A = A
        type F[X] = X
        def w(a: A) = a
        val f = implicitly[Functor[Id]]
      }
    }
    object Helper extends Helper1 {
      implicit def cons[FA1, RA](implicit u: Unapply[Functor, FA1]{type A = RA},
        rest: Helper[RA]) = new Helper[FA1] {
        type A = rest.A
        type F[X] = u.M[rest.F[X]]
        def w(fa: FA1) = u.TC.map(u.apply(fa))(rest.w)
        val f = rest.f.compose(u.TC) //or the other way around, I can never remember
      }
    }
    implicit def compositeOps[FA, A1](self: FA)(
      implicit helper: Helper[FA]{type A = A1}, conv: A1 => Elem) = {
        implicit val FG = helper.f
        new Ops[helper.F, helper.A](helper.w(self))
    }
    

    【讨论】:

    • 感谢您的回复!我发现您的解决方案有两个问题:1)我确实需要从 A 隐式转换为固定的 Elem 类型,如示例中所示。除了在Helper 的正文中包含A =&gt; Elem 隐含作为val 之外,还有什么方法可以处理它和您的Helper? 2) 编译器找不到函子本身的子类的 Helper 实例,例如我无法将Ops 应用于Some。有什么办法可以解除这个限制吗?我知道 Scalaz 类型类已经发生了这种情况,但就我而言,能够做到这一点尤为重要。
    • 1) &lt;% 只是隐含 A =&gt; Elem 的糖;我已将其添加到示例中的复合操作中。 2)这是不可能的;这是 scalaz 中正在进行的辩论,但如果我们声明事物是协变/逆变的,那么这将导致推断出 Any 而不是应该是类型错误的情况。可以声明Functor[Some],但这可能会给您带来麻烦。推荐的方法是使用像scalaz的some这样的“智能构造函数”,它返回适当的类型(在这种情况下是Option[A]而不是Some[A])。
    • 我知道&lt;%,但不知道如何获取A1的类型,也没有想到做{type A = A1}。我想我会解决子类问题。谢谢!
    【解决方案2】:

    我们可以创建一个特征来代表你想要的

    trait DeepFunctor[X, A] {
      type Result[_]
      def map[B](x: X)(f: A => B): Result[B]
    }
    

    请注意,它允许将X 映射到Result[B]X 在这一点上可以是任何东西。

    最简单的版本是我们说X 等于A。结果应该是Id[B]。它被放入一个 trait 以确保它具有低优先级。

    trait LowerPriorityDeepFunctor {
      implicit def identity[A] =
        new DeepFunctor[A, A] {
          type Result[x] = Id[x]
          def map[B](x: A)(f: A => B) = f(x)
        }
    }
    

    请注意,IdFunctor 无需询问。

    更复杂的版本是X 是定义了Functor 的某个容器。

    object DeepFunctor extends LowerPriorityDeepFunctor {
      implicit def deep[F[_], X, A](
        implicit F: Functor[F], inner: DeepFunctor[X, A]) =
        new DeepFunctor[F[X], A] {
          type Result[x] = F[inner.Result[x]]
          def map[B](x: F[X])(f: A => B) = F.map(x)(inner.map(_)(f))
        }
    }
    

    deep 方法的结果是DeepFunctor 对应于F[X]。由于我们对X 一无所知,我们为X 请求DeepFunctor。这将递归搜索DeepFunctor 实例,直到找到identity

    您的Ops 类现在变得相对简单

    implicit class Ops[X](self: X) {
      def foo[A](implicit F: DeepFunctor[X, A]) = F.map(self)(_ => "bar")
    }
    

    请注意,_ 现在的类型为 A。如果要限制为某种类型,可以将A 定义为A &lt;: SomeType。如果您希望能够支持隐式转换,您可以使用额外的隐式参数ev: A =&gt; SomeType。如果你想让A成为一个特定的类型,你可以把A去掉,直接把SomeType放到DeepFunctor里面。

    【讨论】:

    • 这看起来是一个干净且相对容易理解的解决方案!唯一让我有点困扰的是,我必须在每个方法中而不是在类本身中请求一个隐式 DeepFunctor,因为我有不止一个像 foo 这样的操作,而且我必须更改他们的所有签名.我试图将AF 移动到类签名中,但我没有运气,因为每个方法的结果都取决于DeepFunctor 的类型,并且客户端代码不会将其类型解析为原始仿函数组合。你有什么办法可以避免这种情况吗?如果不是那没关系:)
    • @RuiGonçalves 是的,看来问题是由type Result[x] = F[inner.Result[x]] 引起的。我认为这是一个编译器错误,但我不确定。如果隐式移动到类本身,Result[x] 类型就会丢失。我尝试将 Result 类型移动到 DeepFunctor[X, A, R[_]] 但这搞砸了隐式解析。从理论上讲,这本身就是一个堆栈溢出问题。但是,我没有时间创建一个简单的可重现示例来说明这个问题。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-11-15
    • 2017-09-15
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多