【问题标题】:Proper implementation for a 2 type parameters Functor in Scala在 Scala 中正确实现 2 类型参数 Functor
【发布时间】:2019-03-27 14:50:09
【问题描述】:

我在SO上多次看到这个问题,但无论我怎么努力,我都无法编译下面的代码。目标是实现Functor 实现更简单的Reader(代码为here):

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

  implicit class FunctorOps[F[_]: Functor, A](self: F[A]) {
    def fmap[B](f: A => B): F[B] = implicitly[Functor[F]].fmap(self)(f)
  }

  case class Reader[A, B](run: A => B)
  type ReaderF[X] = ({ type L[A] = Reader[X, A] })

  implicit def readerFunctors[E]: Functor[ReaderF[E]#L] = 
    new Functor[ReaderF[E]#L] {
       override def fmap[A, B](fa: Reader[E, A])(f: A => B): Reader[E, B] = 
          Reader(e => f(fa.run(e)))
    }

  val foo = Reader[String, Int](_ => 42)

  foo.fmap(_ + 1) // does not compile

我尝试通过以下方式绕过隐式机制:

FunctorOps(foo).fmap(_ + 1)

但这会输出以下编译错误:

Error:(82, 23) type mismatch;
 found   : com.fp.Scratchpad.Reader[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 com.fp.Scratchpad.Reader[String,Int] to ?F[?A]
  FunctorOps(foo).fmap(_ + 1)

提前感谢您的帮助。

更新

为了确保我的 FunctorOps 是正确的,我为 Id 创建了一个仿函数实例:

case class Id[A](value: A)
implicit val idF: Functor[Id] = new Functor[Id] {
  override def fmap[A, B](fa: Id[A])(f: A => B): Id[B] = Id(f(fa.value))
}

val id = Id(42)
id.fmap(_ + 1) // compiles

所以问题不是来自FunctorOps 隐式类。我怀疑 Scala 在使用 lambda 类型时会遇到困难......

更新 2

我试图简化问题但没有成功:

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

  implicit class Ops[F[_], A](fa: F[A])(implicit F: Functor[F]) {
    def map[B](f: A => B): F[B] = F.map(fa)(f)
  }

  type FF[A] = ({ type F[B] = A => B })

  implicit def ff[E]: Functor[FF[E]#F] = new Functor[FF[E]#F] {
    override def map[A, B](x: E => A)(f: A => B): E => B = e => f(x(e))
  }

  val f: String => Int = _ => 42

  val value: Functor[FF[String]#F] = ff[String]
  val ops = new Ops[FF[String]#F, Int](f)(value)

  // These compile
  ops.map(_ + 1)("")
  value.map(f)(_ + 1)("")

  // This not
  f.map(_ + 1)

【问题讨论】:

  • 你的 Scala 版本是多少?第一个编译在scastie.scala-lang.org/gpRKdKjNS4KLTAq7Zwptsw。实际上我很惊讶它似乎也可以在旧版本中使用,我认为您需要 2.12 或 2.11.11+。
  • 我刚刚检查过,Scala 版本是 2.12.8。尝试用 IDEA 和 sbt 编译 :(
  • 你是在工作表中运行这个吗?
  • 不,我也尝试编译这个原始 sbt

标签: scala intellij-idea functional-programming


【解决方案1】:

更新:
我认为,要使其正常工作,您需要在build.sbt 中为编译器启用一些额外选项:

scalacOptions ++= Seq(
      "-Ypartial-unification",
      "-language:postfixOps",
      "-language:higherKinds",
      "-deprecation",
      "-encoding", "UTF-8",
      "-feature",      
      "-unchecked"
    )

关于部分统一标志的更多信息以及它解决了什么can be found here

原始答案: 您是通过 Worksheet 还是 IDEA 中的 Scratch 运行代码?我注意到有时,特别是在这类函数式编程任务中,其中存在类型推断、隐式解析和更高种类的类型“魔法”,IDEA 的 REPL 无法胜任任务(但我不确定为什么)。

这就是说,我尝试在 IDEA 上运行以下内容:

object TestApp extends App{
  trait Functor[F[_]] {
    def fmap[A, B](fa: F[A])(f: A => B): F[B]
  }

  implicit class FunctorOps[F[_]: Functor, A](self: F[A]) {
    def fmap[B](f: A => B): F[B] = implicitly[Functor[F]].fmap(self)(f)
  }

  case class Reader[A, B](run: A => B)
  type ReaderF[X] = ({ type L[A] = Reader[X, A] })

  implicit def readerFunctors[E]: Functor[ReaderF[E]#L] =
    new Functor[ReaderF[E]#L] {
      override def fmap[A, B](fa: Reader[E, A])(f: A => B): Reader[E, B] =
        Reader(e => f(fa.run(e)))
    }

  val foo: Reader[String, Int] = Reader[String, Int](s => s.length)

  val i = foo.fmap(_ + 1)

  println(i.run("Test"))
  println(i.run("Hello World"))
}

它工作正常,打印512。此外,正如其他人所提到的,您的代码适用于 Scastie,这是 IDEA 的另一个表现。

最后一点:您可能已经知道这一点,但您可以使用 kind-projector compiler plugin 避免所有类型 lambda 的丑陋。

长话短说,去掉 ReaderF[X] 类型别名,让你的仿函数实例看起来像这样:

implicit def readerFunctors[X]: Functor[Reader[X,?]] =
    new Functor[Reader[X,?]] {
      override def fmap[B, C](fa: Reader[X,B])(f: B => C): Reader[X,C] =
        Reader(e => f(fa.run(e)))
    }

恕我直言,哪个更具可读性。

【讨论】:

  • 我在 IDEA 上复制粘贴了代码,它甚至无法编译 :( 在 sbt 上相同。我使用的是 scala 2.12.8 和 sbt 1.2.8。我也尝试运行它(没有TestApp) 在标准 REPL 中仍然没有成功。你使用什么版本?
  • scalac 在val i = foo.fmap(_ + 1)也发现了一个错误
  • ....这很奇怪。我使用的是普通配置,Scala 2.12.8,没什么花哨或奇怪的......
  • 但无论哪种方式,我认为现在很清楚,无论问题是什么,它都不在代码中,而是在您的环境中。我猜这是个好消息:)
  • 老实说,我仍然不确定这一点。请给我您正在使用的 sbt/scala 版本吗?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-05-24
  • 2011-10-16
  • 1970-01-01
  • 2011-10-16
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多