Правильная реализация для 2-х типов параметров Functor в Scala - PullRequest
3 голосов
/ 27 марта 2019

Я видел этот вопрос несколько раз на SO, но как бы я ни старался, я не могу заставить следующий код компилироваться. Цель состоит в том, чтобы реализовать реализацию Functor для более простого Reader (код здесь ):

  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)

Заранее благодарю за помощь.

UPDATE

Чтобы убедиться, что мои 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 очень тяжелые времена с типом лямбд ...

ОБНОВЛЕНИЕ 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)

1 Ответ

1 голос
/ 28 марта 2019

ОБНОВЛЕНИЕ:
Я думаю, чтобы это работало, вам нужно включить некоторые дополнительные опции для компилятора в build.sbt:

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

Подробнее офлаг частичное объединение и то, что он решает , можно найти здесь .

ОРИГИНАЛЬНЫЙ ОТВЕТ : Вы выполняете свой код через Worksheet или Scratchв идее?Я заметил, что иногда, особенно в такого рода задачах функционального программирования, где есть вывод типа, неявное разрешение и «магический» тип с более высоким родом, REPL IDEA не соответствуют задаче (но я не уверен почему).

При этом я пытался запустить на 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"))
}

И все работает нормально, печатая 5 и 12.Кроме того, как кто-то еще упомянул, ваш код работает на Scastie, который является еще одним синтезом IDEA.

Последнее замечание: вы, вероятно, уже знаете это, но вы можете избежать всего этого уродства лямбда-типов, используя плагин компилятора kind-проектора .

Короче говоря, отбросьте псевдоним типа 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)))
    }

более читабельное ИМХО.

...