Кошки: Реализация Contravariant для Предикатов без псевдонима типа? - PullRequest
0 голосов
/ 03 апреля 2020

Скажем, что Предикат является функцией A => Boolean, я хочу реализовать экземпляр класса типа Cats "Contravariant Functor" для предикатов. У меня также есть неявный класс PredicateOps, который определяет операторы объединения и пересечения для предикатов.

Мне удалось заставить экземпляр работать, используя псевдоним типа:

type Predicate[A] = A => Boolean

implicit val predicateContra = new Contravariant[Predicate] {
  override def contramap[A, B](fa: Predicate[A])(f: B => A): Predicate[B] =
    (b: B) => fa(f(b))
}

Но когда Я делаю это, я должен привести все свои предикатные функции к псевдониму, подобному следующему:

val even: Predicate[Int] = (i: Int) => i % 2 == 0

, что меня раздражает. Поэтому я подумал, можно ли вместо использования псевдонима типа определить предикат Contra непосредственно для Function1 из переменной типа A в Boolean, но я не смог заставить его работать. Обе следующие идеи дают мне ошибку компилятора:

implicit val predicateContra = new Contravariant[Function1[_, Boolean]] {
// "Function1[_, Boolean] takes no type parameters, expected: one"

implicit def predicateContra[A] = new Contravariant[Function1[A, Boolean]] {
// "A => Boolean takes no type parameters, expected: one"

Как я могу сказать компилятору, что первый параметр моего Function1 должен оставаться «дырой», в то время как второй должен быть зафиксирован как boolean? Это вообще возможно? Просматривая исходный код для кошек, я обнаружил звездочки в качестве параметров типа в нескольких местах, но это тоже не сработало для меня.

1 Ответ

3 голосов
/ 03 апреля 2020

Вы можете использовать добрый проектор , который позволяет ссылаться на «типовое отверстие» звездочкой (*).

Это позволяет использовать очень простой синтаксис для определения типа * -> *, то есть конструктора унарного типа (для получения типа требуется один тип). Например, тип, которому для получения типа Map[A, Int] требуется некоторый тип A, может быть записан просто как Map[*, Int].

Тогда ваш код станет:

val strToBool: String => Boolean = _.contains("1")
val intToStr: Int => String = _.toString

def predicateContra = 
  new Contravariant[Function1[*, Boolean]] {
    override def contramap[A, B](fa: A => Boolean)(f: B => A): B => Boolean = 
      (b: B) => fa(f(b))
  }

predicateContra.contramap(strToBool)(intToStr)(42) // false 
predicateContra.contramap(strToBool)(intToStr)(41) // true

Если вы не хотите использовать дополнительные библиотеки, вы можете сделать это простым Scala несколько более уродливым способом, используя тип lambda:

def predicateContra =
  new Contravariant[({ type lambda[A] = Function1[A, Boolean] })#lambda] {
      ...
  }
...