Как совместить границы параметров типа и функторы с помощью Cats?

Я сталкиваюсь с рядом случаев использования, когда я заканчиваю попытки написать экземпляры 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 и т. д. для контекстов, в которых я также использую параметры типа? Это вообще возможно? Есть ли более подходящий путь вперед?


person davidrpugh    schedule 24.07.2018    source источник
comment
Если SocialWelfareFunction[C, ?] должен быть Functor во втором компоненте, то он должен иметь map, который принимает f: A => B и отображает SocialWelfareFunction[C, A] в SocialWelfareFunction[C, B]. Можете ли вы представить себе map-реализацию, которая может отображать Preference[_] в List[Int] или JsonNode или (Unit, Preference[_]) или Either[String, Double]? Если нет, то это не функтор. Тщательный выбор типов A и B не сильно поможет, потому что многие интересные конструкции функторов и монад полагаются на то, что функтор F может отображать F[A], например. F[Either[X, A]].   -  person Andrey Tyukin    schedule 24.07.2018
comment
Возможно, я действительно хочу сказать не то, что P <: Preference[A], а то, что P имеет определенный экземпляр класса типов Order[A]. Возможно ли это?   -  person davidrpugh    schedule 24.07.2018
comment
Вот пример с typeclass (там требовался Encoder[A] вместо Order[A], но чем-то похоже). На самом деле должна быть возможность аналогичным образом получить функтор для конструкторов типов, которые имеют ограничения на параметры. Я набросаю предложение (вероятно, это займет несколько минут).   -  person Andrey Tyukin    schedule 24.07.2018
comment
У меня есть общая демонстрация того, как определить экземпляры Functor для конструкторов типов, которые имеют границы типов и зависят от классов типов, но я до сих пор не понимаю, чего вы хотели добиться с помощью этого SocialWelfareFunction. Кажется, вы хотели определить функтор F[P] = (Iterable[P] => P), что невозможно (поскольку P является одновременно входом и выходом, что делает его очень похожим на Endomorphisms[P], который также не является функтором, независимо от системы типов или языка).   -  person Andrey Tyukin    schedule 24.07.2018
comment
Я изучаю FP, создавая библиотеку для моделирования аукциона. Очень возможно, что мой SocialWelfareFunction просто не функтор. Возможно, я смогу найти лучший пример, чтобы продемонстрировать взаимодействие между границами параметров типа (или границами класса типа) при создании функторов, которые находятся в центре внимания этого вопроса.   -  person davidrpugh    schedule 24.07.2018
comment
Можете ли вы опубликовать свою демонстрацию? Возможно, просмотр демонстрации поможет мне решить вопрос в лучшем направлении...   -  person davidrpugh    schedule 24.07.2018
comment
@davidpugh Опубликовал общее решение для почти функторов с верхними границами типов и классами типов. По сути, это то же самое, что и в ответе, связанном ранее. Я думаю, что преобразую его в каноническую пару вопросов и ответов, на которую я могу сослаться позже, потому что этот вопрос, кажется, возникает неоднократно.   -  person Andrey Tyukin    schedule 24.07.2018


Ответы (1)


(Не полное решение, просто подсказка, запрошенная OP для дальнейшего уточнения вопроса)

В этом примере показано, как определить экземпляры Functor для конструкторов типов, которые выглядят почти так, как если бы они могли быть функторами, но имеют несколько ограничений:

  1. Верхняя граница типа <: 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]"
}
person Andrey Tyukin    schedule 24.07.2018