【问题标题】:How to combine type parameter bounds and functors using Cats?如何使用 Cats 组合类型参数边界和仿函数?
【发布时间】:2018-07-24 06:53:26
【问题描述】:

我遇到了许多用例,在这些用例中,我已经在使用类型参数边界的上下文中尝试编写 Functor、Applicative、Monad 等实例。

例如...

import cats._

trait Preference[A] extends Order[A]

trait SocialWelfareFunction[-CC <: Iterable[P], +P <: Preference[_]]
  extends (CC => P)


object SocialWelfareFunction {

  def functor: Functor[({ type F[P <: Preference[_]] = SocialWelfareFunction[Iterable[P], P] })#F] = {
    ???
  }

...当我尝试编译它时,我会收到以下错误。

kinds of the type arguments ([P <: Playground.this.Preference[_]]Playground.this.SocialWelfareFunction[Iterable[P],P]) do not conform to the expected kinds of the type parameters (type F) in trait Monad.
[P <: Playground.this.Preference[_]]Playground.this.SocialWelfareFunction[Iterable[P],P]'s type parameters do not match type F's expected parameters:
type P's bounds <: Playground.this.Preference[_] are stricter than type _'s declared bounds >: Nothing <: Any

如何为我也使用类型参数的上下文编写 Functor、Applicative、Monad 等实例?甚至可能吗?有没有更合适的前进方式?

【问题讨论】:

  • 如果SocialWelfareFunction[C, ?] 应该是第二个组件中的Functor,那么它必须有一个接受f: A =&gt; B 并将SocialWelfareFunction[C, A] 映射到SocialWelfareFunction[C, B] 的map。你能想象一个map-实现可以将Preference[_]映射到List[Int]或JsonNode或(Unit, Preference[_])或Either[String, Double]吗?如果不是,那么它就不是函子。仔细挑选类型 A 和 B 不会有太大帮助,因为函子和 monad 上的许多有趣结构依赖于函子 F 能够将 F[A] 映射到例如F[Either[X, A]].
  • 也许我真正想说的不是P &lt;: Preference[A],而是P 定义了一个类型类Order[A] 的实例。这可能吗?
  • 这里是example with typeclass(这里需要Encoder[A] 而不是Order[A],但有点相似)。实际上,应该有可能以类似的方式为具有参数界限的类型构造函数获取“函子”。我会草拟一份提案(可能需要几分钟)。
  • 我有一个关于如何为具有类型边界并依赖于类型类的类型构造函数定义 Functor 实例的一般演示,但我仍然不明白你想用 @987654353 在那里实现什么@。看来你想定义一个仿函数F[P] = (Iterable[P] =&gt; P),这是不可能的(因为P既是输入又是输出,这使得它与Endomorphisms[P]非常相似,它也不是仿函数,无论类型系统或语言)。
  • @davidpugh 发布了具有上限类型边界和类型类的“几乎函子”的通用解决方案。它与之前链接的答案基本相同。我想我会将其转换为“规范的”问答对,稍后我可以链接到该对,因为这个问题似乎反复出现。

标签: scala functional-programming functor type-parameter scala-cats


【解决方案1】:

(不是完整的解决方案,只是 OP 要求进一步澄清问题的提示)

此示例说明如何为类型构造函数定义 Functor 实例,这些实例看起来几乎可以是仿函数,但有几个限制:

  1. 类型参数上的类型上限&lt;: UB
  2. 需要类型类的实例TC

这是绕过这两个限制的方法:

import scala.language.higherKinds

// Your favorite flavour of `Functor`,
// e.g. from `scalaz` or `cats`
trait Functor[F[_]] {
  def map[A, B](x: F[A], f: A => B): F[B]
}

// an upper bound
trait UB

// a typeclass
trait TC[X]


// A type constructor that is not a 
// functor, because it imposes upper bounds
// on the parameter, and because it requires
// a typeclass `TC` for `X`.
class Foo[X <: UB : TC] {
  def map[Y <: UB : TC](f: X => Y): Foo[Y] = ??? /* 
    some very clever implementation making use of `UB` and `TC`
  */
}

// A Functor that approximates `Foo[X]` for *arbitrary* `X`,
// without any restrictions.
abstract class WrappedFoo[X] { outer =>
  type Base <: UB
  val base: Foo[Base]

  protected val path: Base => X

  def map[Y](f: X => Y): WrappedFoo[Y] = new WrappedFoo[Y] {
    type Base = outer.Base
    val base = outer.base
    val path: Base => Y = outer.path andThen f
  }

  def unwrap[Y <: UB](
    implicit
    xIsY: X =:= Y,
    yTC: TC[Y]
  ): Foo[Y] = base.map(outer.path andThen xIsY)
}

// Functor instance for `WrappedFoo`
object WrappedFooFunctor extends Functor[WrappedFoo] {
  def map[A, B](x: WrappedFoo[A], f: A => B): WrappedFoo[B] = {
    x map f
  }
  def wrap[A <: UB](foo: Foo[A]): WrappedFoo[A] = new WrappedFoo[A] {
    type Base = A
    val base = foo
    val path = identity[A]
  }
}

object Example {

  // two "good" classes that conform to 
  // the upper bound and have instances
  // of the typeclass
  class Good1 extends UB
  class Good2(i: Int) extends UB

  implicit object Good1TC extends TC[Good1]
  implicit object Good2TC extends TC[Good2]

  val x1 = new Foo[Good1]       // start with "Foo[Good1]"
  val f: Good1 => Int = _.toString.length
  val g: Int => Good2 = i => new Good2(i)

  // Now we would like to go like this:
  // 
  //   Foo[Good1] ---f---> Foo[Int] ---g---> Foo[Good2]
  // 
  // The problem is: `Int` does not conform to `UB`,
  // and has no `TC` instance.

  // Solution:
  val x1w = WrappedFooFunctor.wrap(x1)
  val intermediate = x1w.map(f) // approximates "Foo[Int]"
  val x2w = intermediate.map(g) // wraps "Foo[Good2]"
  val x2 = x2w.unwrap           // only "Foo[Good2]"
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2022-12-09
    • 2019-03-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-09-30
    • 1970-01-01
    • 2021-06-24
    相关资源
    最近更新 更多