Scala: как использовать функторы для типажей параметров нескольких типов

У меня есть и ADT, который в основном является оболочкой над Function1:

case class Abstract[M[_], A, B](f:M[A] => M[B]) {
    def fn: M[A] => M[B] = { case x: M[A] => f(x) }
}

Я хочу отобразить их, поэтому я определил Functor следующим образом:

trait AbstractAPI[E] {
    type AbsO[T] = Abstract[List, E, T]
    // type AbsO[T] = Abstract[List, _, T] => does not work (?)

    implicit val abstractO: Functor[AbsO] = new Functor[AbsO] {
        def map[A, B](fa: AbsO[A])(f: A => B): AbsO[B] = {
            new Abstract(fa.fn andThen { x: List[A] => x.map{ y => f(y) } })
        }
    }
}

Теперь, чтобы на самом деле сопоставить Abstract, мне понадобится AbstractAPI[Int], например

case object IntAbstractAPI extends AbstractAPI[Int]

object A {
    import IntAbstractAPI._

    val f:List[Int] => List[String] = { case x: List[Int] => x.map{ _.toString.toLowerCase } }
    val hey = (new Abstract(f)).map{ x => x.toInt }
}

or

object A extends AbstractAPI[Int] {

    val f:List[Int] => List[String] = { case x: List[Int] => x.map{ _.toString.toLowerCase } }

    // FINALLY!
    val res = (new Abstract(f)).map{ x => x.toInt }.map{ _.toFloat + 10f }
    // Abstract[List, Int, Float] = Abstract(<function1>)
}

Однако в этом шаблоне мне пришлось бы определять объекты case для всех возможных E. Вот мои вопросы:

  1. Это правильный способ использования функторов?
  2. Как я могу автоматизировать создание объектов case для всех возможных E (или заставить компилятор сделать вывод?)

Редактировать 1: Дальнейшее уточнение: вышеприведенная реализация работает, а эта нет:

object A extends AbstractAPI {

    val f:List[Int] => List[String] = { case x: List[Int] => x.map{ _.toString.toLowerCase } }

    val res = (new Abstract(f)).map{ x => x.toInt }.map{ _.toFloat + 10f }
    // Abstract[List, Int, Float] = Abstract(<function1>)
}

выдает ошибку компиляции:

value map is not a member of Abstract[List,Int,String]

Я предполагаю, что это потому, что компилятор не может получить функтор для Abstract[List,Int,String]?


person ixaxaar    schedule 18.06.2017    source источник
comment
Это немного сложно понять. Компилятор никак не может знать, как преобразовать _ в T, не зная, как это сделать, верно?   -  person erip    schedule 19.06.2017
comment
в основном я хочу создать оболочку для функции таким образом, чтобы я мог сопоставлять оболочку с другими функциями для создания новых оболочек (составленных) функций   -  person ixaxaar    schedule 19.06.2017
comment
Это помогает?   -  person ixaxaar    schedule 19.06.2017
comment
Я не совсем уверен в этом, это означало бы, что это тупик. Это все еще правильный путь, или я где-то в корне не прав?   -  person ixaxaar    schedule 19.06.2017
comment
Что вы обычно делаете, так это определяете объекты case для примитивов, а затем используете shapeless для их получения для любого case class/ADT.   -  person Yuval Itzchakov    schedule 19.06.2017
comment
Вы имеете в виду автоматическое создание экземпляров класса типов, например библиотеку github.com/milessabin/kittens?   -  person ixaxaar    schedule 19.06.2017
comment
Разве это не простое приложение?   -  person pedrofurla    schedule 19.06.2017
comment
Или это просто несколько запутанное переопределение функторов?   -  person pedrofurla    schedule 19.06.2017
comment
Почему вам нужно, чтобы результирующий тип был мономорфным? Это то, что кажется запутанным, любое приложение Functor#map приведет к конкретному значению, для этого нет необходимости проходить через косвенные действия.   -  person pedrofurla    schedule 19.06.2017
comment
Я не хочу, чтобы они были мономорфными, скорее я хочу этого избежать, проблема в том, что компилятор не может вывести типы (или я что-то упустил?)   -  person ixaxaar    schedule 19.06.2017


Ответы (2)


Вы можете получить функтор для параметров типа, которые вам не нужны.

import cats.Functor
import cats.syntax.functor._

И я переименую параметр второго типа на Abstract в X, это поможет

case class Abstract[M[_], X, A](f: M[X] => M[A]) // forget the fn bit for now

Вы можете создавать экземпляры класса типов не только с val, но и с def. Допускается иметь параметры типа, а также принимать другие неявные (но только неявные) параметры.

type Abs1[X] = ({ type L[A] = Abstract[List, X, A] })

/*implicit*/ def abstract1[X]: Functor[Abs1[X]#L] = new Functor[Abs1[X]#L] {
override def map[A, B](fa: Abstract[List, X, A])(f: A => B): Abstract[List, X, B] =
      Abstract(mx => fa.f(mx).map(f))
}

Если map — это все, что вам нужно от List, вы можете сделать дальнейшие обобщения для любого M[_], имеющего экземпляр Functor. Также размещение его в сопутствующем объекте Abstract позволяет найти его без дополнительного импорта/наследования/и т. д.

object Abstract {
  // Abstract.MX[M, X]#L can be replaced with Abstract[M, X, ?] if you use kind-projector
  type MX[M[_], X] = ({ type L[A] = Abstract[M, X, A] })

  implicit def genericFunctor[M[_]: Functor, X] = new Functor[MX[M, X]#L] {
    override def map[A, B](fa: Abstract[M, X, A])(f: A => B): Abstract[M, X, B] =
      Abstract(mx => fa.f(mx).map(f)) // the implementation is the same
  }
}

И это работает, если вы импортируете экземпляры для любого вашего M[_].

assert {
  import cats.instances.list._ // get Functor[List]

  // map is automatically picked up from Functor[Abstract[List, Int, ?]]
  Abstract(identity[List[Int]])
    .map(Vector.range(0, _))
    .map(_.mkString(""))
    .f(List(1, 2, 3)) == List("0", "01", "012")
}

assert {
  import cats.instances.option._

  Abstract(identity[Option[Int]])
    .map(_ min 42)
    .map(i => Range(i, i + 3))
    .f(Some(11)) == Some(Range(11, 14))
}

Вы можете попробовать код здесь

person Oleg Pyzhcov    schedule 12.07.2017

Отвечая на ваш второй вопрос, вы можете попробовать эту неявную фабрику AbstractAPI[T]:

implicit def abstractAPI[T]: AbstractAPI[T] = new AbstractAPI[T] {}

Любые требуемые неявные доказательства для AbstractAPI[T] должны работать, например:

def f[T : AbstractAPI]: Unit = ()
f
person Pablo Francisco Pérez Hidalgo    schedule 22.06.2017