【问题标题】:Compilation error when declaring Functor for Either even after using type projections即使在使用类型投影后为 Either 声明 Functor 时也会出现编译错误
【发布时间】:2017-09-25 13:16:59
【问题描述】:

我正在尝试为 Either 写一个 Functor 用于 Scala 中的学术目的。在higher-kinded 类型和type-projections 的帮助下,我设法为Either 编写了一个实现。

trait Functor[F[_]] {
  def map[A, B](fa: F[A])(f: A => B): F[B]
}

object Functor {
  implicit def eitherFunctor[A] = new Functor[({type λ[α] = Either[A, α]})#λ] {
    override def map[B, C](fa: Either[A, B])(f: B => C) = fa.map(f)
  }
}

def mapAll[F[_], A, B](fa: F[A])(f: A => B)(implicit fe: Functor[F]): F[B] = fe.map(fa)(f)

val right: Either[String, Int] =  Right(2)

mapAll(right)(_ + 2)

现在,上面的代码无法编译。我不确定原因,但我得到的编译错误如下 -

Error:(19, 16) type mismatch;
 found   : Either[String,Int]
 required: ?F[?A]
Note that implicit conversions are not applicable because they are ambiguous:
 both method ArrowAssoc in object Predef of type [A](self: A)ArrowAssoc[A]
 and method Ensuring in object Predef of type [A](self: A)Ensuring[A]
 are possible conversion functions from Either[String,Int] to ?F[?A]
  mapAll(right)(_ + 2)

有人可以指出我在上面的代码中没有做对吗?

PS:请不要建议我使用kind-projector

【问题讨论】:

  • Scala 不支持部分应用的类型构造函数。如果一个方法需要一个F[_],你必须给它一个F[_],而不是一个F[String, _]
  • @AnshulBajpai 您当前版本的代码在 2.12.3 中编译和运行:mapAll(right)(_ + 2) //Right(4)
  • @DmytroMitin - 我正在使用 scala 2.12.3 我认为它会工作,但它没有。您是否必须打开任何 scala 编译器选项?
  • @AnshulBajpai 是的,你是对的。我的 build.sbt:scalaVersion := "2.12.3" scalacOptions ++= Seq("-language:higherKinds", "-language:reflectiveCalls", "-Ypartial-unification")。抱歉误导。
  • @DmytroMitin - 仅需要 Ypartial-unification 选项才能使编译通过。非常感谢。

标签: scala functor higher-kinded-types type-projection


【解决方案1】:

你刚刚被 SI-2712 咬了。如果您使用的是 Scala >= 2.12.2,只需将此行添加到您的 build.sbt

scalacOptions += "-Ypartial-unification"

对于其他 Scala 版本,您可以使用this plugin

【讨论】:

    【解决方案2】:

    Either[+A, +B] 期待两个 类型参数(正如@Ziyang Liu 所说),因此对于您的示例实际上需要 BiFunctor 而不是 Functor, BiFunctor 接受两个函子并绑定 两种。

    Bifunctor 来自Scalaz

    trait Bifunctor[F[_, _]]  { self =>
      ////
    
      /** `map` over both type parameters. */
      def bimap[A, B, C, D](fab: F[A, B])(f: A => C, g: B => D): F[C, D]
    

    所以你可以像这样使用Bifunctor

    Bifunctor[Either].bimap(Right(1): Either[String, Int])(_.toUpperCase, _ + 1).println
    

    希望对你有帮助。

    【讨论】:

    • 谢谢,我读过Bifunctors,这是做类似事情的另一种方式,但不是我想要实现的实际目标。但目前,我不是在寻找 Bifunctor 解决方案。我很确定这可以通过使用类型投影来解决。缺点是这个实现会偏右,但也许这就是我想要的。另外,我想知道我的代码出了什么问题,这将使我对问题有更多的了解。如果我想使用库,我可以简单地使用cats,它给出了Either 函子。但正如我所说,我做这个练习是出于学术原因。
    【解决方案3】:

    正如其他人所说,编译器试图告诉您的是您的类型的形状不匹配。当您需要 F[_] 时,您需要一个具有单个类型参数的类型构造函数,而 Either[A, B] 不满足。

    我们需要做的是在应用 mapAll 时应用一个类型 lambda,就像我们在创建 Either 函子的实例时所做的一样:

    val right: Either[String, Int] = Right(2)
    
    mapAll[({type λ[α]=Either[String, α]})#λ, Int, Int](right)(_ + 2)
    

    我们现在挤入String 并将其固定为第一个参数,允许类型投影类型只需要满足我们的alpha,现在形状匹配。

    当然,我们也可以使用类型别名,这样我们在应用mapAll 时就无需指定任何额外的类型信息:

    type MyEither[A] = Either[String, A]
    val right: MyEither[Int] = Right(2)
    
    mapAll(right)(_ + 2)
    

    【讨论】:

    • 你提到的是我第一次猜测为什么它不起作用。如果Left 已修复,您建议对类型别名执行的操作将是正确的做法。但我们想概括它,@lambdista 和@DmytroMitin 建议的解决方案确实有效。但感谢您详细阐述我所面临的问题。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-01-12
    • 1970-01-01
    • 1970-01-01
    • 2019-03-27
    • 1970-01-01
    • 1970-01-01
    • 2016-10-14
    相关资源
    最近更新 更多