Подтвердить что ты не робот

Использование границ контекста "отрицательно", чтобы гарантировать, что экземпляр класса экземпляра отсутствует в области видимости

tl; dr: как мне сделать что-то вроде составленного кода ниже:

def notFunctor[M[_] : Not[Functor]](m: M[_]) = s"$m is not a functor"

"Not[Functor]", являющийся составной частью здесь.
Я хочу, чтобы он преуспел, когда "m" предоставлен не Functor, и в противном случае не скомпрометирует компилятор.

Решено: пропустите оставшуюся часть вопроса и перейдите прямо к ответу ниже.


То, что я пытаюсь выполнить, грубо говоря, "отрицательное доказательство".

Псевдокод будет выглядеть примерно так:

// type class for obtaining serialization size in bytes.
trait SizeOf[A] { def sizeOf(a: A): Long }

// type class specialized for types whose size may vary between instances
trait VarSizeOf[A] extends SizeOf[A]

// type class specialized for types whose elements share the same size (e.g. Int)
trait FixedSizeOf[A] extends SizeOf[A] {
  def fixedSize: Long
  def sizeOf(a: A) = fixedSize
}

// SizeOf for container with fixed-sized elements and Length (using scalaz.Length)
implicit def fixedSizeOf[T[_] : Length, A : FixedSizeOf] = new VarSizeOf[T[A]] {
  def sizeOf(as: T[A]) = ... // length(as) * sizeOf[A]
}

// SizeOf for container with scalaz.Foldable, and elements with VarSizeOf
implicit def foldSizeOf[T[_] : Foldable, A : SizeOf] = new VarSizeOf[T[A]] {
  def sizeOf(as: T[A]) = ... // foldMap(a => sizeOf(a))
}

Имейте в виду, что fixedSizeOf() предпочтительнее, если это необходимо, поскольку это избавляет нас от обхода коллекции.

Таким образом, для типов контейнеров, где определен только Length (но не Foldable), а для элементов, где a FixedSizeOf определен, мы получаем улучшенную производительность.

В остальных случаях мы просматриваем коллекцию и суммируем индивидуальные размеры.

Моя проблема заключается в случаях, когда для контейнера определены как Length, так и Foldable, а для элементов - FixedSizeOf. Это очень распространенный случай здесь (например: List[Int] имеет оба определения).

Пример:

scala> implicitly[SizeOf[List[Int]]].sizeOf(List(1,2,3))
<console>:24: error: ambiguous implicit values:
 both method foldSizeOf of type [T[_], A](implicit evidence$1: scalaz.Foldable[T], implicit evidence$2: SizeOf[A])VarSizeOf[T[A]]
 and method fixedSizeOf of type [T[_], A](implicit evidence$1: scalaz.Length[T], implicit evidence$2: FixedSizeOf[A])VarSizeOf[T[A]]
 match expected type SizeOf[List[Int]]
              implicitly[SizeOf[List[Int]]].sizeOf(List(1,2,3))

Я хотел бы иметь возможность полагаться на класс типа Foldable только тогда, когда комбинация Length + FixedSizeOf не применяется.

С этой целью я могу изменить определение foldSizeOf() для принятия элементов VarSizeOf:

implicit def foldSizeOfVar[T[_] : Foldable, A : VarSizeOf] = // ...

И теперь мы должны заполнить проблематичную часть, которая охватывает контейнеры Foldable с элементами FixedSizeOf и no Length. Я не уверен, как подойти к этому, но псевдокод будет выглядеть примерно так:

implicit def foldSizeOfFixed[T[_] : Foldable : Not[Length], A : FixedSizeOf] = // ...

"Not[Length]", очевидно, является составной частью здесь.

Частичные решения, о которых я знаю

1) Определите класс для невысоких приоритетов и продолжите его, как показано в 'object Predef extends LowPriorityImplicits'. Последний неявный (foldSizeOfFixed()) может быть определен в родительском классе и будет переопределен альтернативой из класса потомков.

Мне неинтересен этот параметр, потому что я хотел бы в конечном итоге поддерживать рекурсивное использование SizeOf, и это предотвратит использование неявного базового класса с низким приоритетом в подклассном классе (это мое понимание здесь правильно? EDIT: неправильный! неявный поиск работает из контекста подкласса, это жизнеспособное решение!)

2) Более грубый подход основан на Option[TypeClass] (например,: Option[Length[List]]. Некоторые из них и я могу просто написать один большой неявный символ, который выбирает Foldable и SizeOf как обязательные, и Length и FixedSizeOf как необязательные, и полагаются на последние, если они доступны. (источник: здесь)

Две проблемы здесь - отсутствие модульности и возврат к исключениям времени выполнения, когда не могут быть найдены соответствующие экземпляры класса типа (этот пример, вероятно, может быть сделан для работы с этим решением, но это не всегда возможно)

EDIT: Это лучшее, что я смог получить с дополнительными implicits. Это еще не все:

implicit def optionalTypeClass[TC](implicit tc: TC = null) = Option(tc)
type OptionalLength[T[_]] = Option[Length[T]]
type OptionalFixedSizeOf[T[_]] = Option[FixedSizeOf[T]]

implicit def sizeOfContainer[
    T[_] : Foldable : OptionalLength,
    A : SizeOf : OptionalFixedSizeOf]: SizeOf[T[A]] = new SizeOf[T[A]] {
  def sizeOf(as: T[A]) = {

    // optionally calculate using Length + FixedSizeOf is possible
    val fixedLength = for {
      lengthOf <- implicitly[OptionalLength[T]]
      sizeOf <- implicitly[OptionalFixedSizeOf[A]]
    } yield lengthOf.length(as) * sizeOf.fixedSize

    // otherwise fall back to Foldable
    fixedLength.getOrElse { 
      val foldable = implicitly[Foldable[T]]
      val sizeOf = implicitly[SizeOf[A]]
      foldable.foldMap(as)(a => sizeOf.sizeOf(a))
    }
  }
}

За исключением того, что раньше он сталкивался с fixedSizeOf(), что по-прежнему необходимо.

Спасибо за любую помощь или перспективу: -)

4b9b3361

Ответ 1

В конечном итоге я решил использовать это с использованием решения на основе двусмысленности, которое не требует приоритизации использования наследования.

Вот моя попытка обобщить это.

Мы используем тип Not[A] для построения классов отрицательного типа:

import scala.language.higherKinds

trait Not[A]

trait Monoid[_] // or import scalaz._, Scalaz._
type NotMonoid[A] = Not[Monoid[A]] 

trait Functor[_[_]] // or import scalaz._, Scalaz._
type NotFunctor[M[_]] = Not[Functor[M]]

... который затем может использоваться как границы контекста:

def foo[T: NotMonoid] = ...

Мы продолжаем, гарантируя, что каждое допустимое выражение Not [A] получит хотя бы один неявный экземпляр.

implicit def notA[A, TC[_]] = new Not[TC[A]] {}

Экземпляр называется "notA" - "not", потому что если он является единственным экземпляром, найденным для "Not [TC [A]]", то применяется класс отрицательного типа; "A" обычно прилагается к методам, которые относятся к типам плоской формы (например, Int).

Теперь мы вводим двусмысленность, чтобы отменить случаи, когда применяется класс нежелательного типа:

implicit def notNotA[A : TC, TC[_]] = new Not[TC[A]] {}

Это почти то же самое, что и "NotA", но здесь нас интересуют только те типы, для которых экземпляр класса типа, указанный "TC", существует в неявной области. Экземпляр называется "notNotA", поскольку, просто сопоставляя неявное выражение, оно создаст неоднозначность с "notA", если неявный поиск (это наша цель).

Перейдите к примеру использования. Мы будем использовать класс отрицательного типа "NotMonoid" сверху:

implicitly[NotMonoid[java.io.File]] // succeeds
implicitly[NotMonoid[Int]] // fails

def showIfNotMonoid[A: NotMonoid](a: A) = a.toString

showIfNotMonoid(3) // fails, good!
showIfNotMonoid(scala.Console) // succeeds for anything that isn't a Monoid

Пока все хорошо! Однако типы, сформированные M [_] и типы классов, сформированные TC [_]], еще не поддерживаются приведенной выше схемой. Также добавьте имплициты:

implicit def notM[M[_], TC[_[_]]] = new Not[TC[M]] {}
implicit def notNotM[M[_] : TC, TC[_[_]]] = new Not[TC[M]] {}

implicitly[NotFunctor[List]] // fails
implicitly[NotFunctor[Class]] // succeeds

Прост достаточно. Обратите внимание, что Scalaz имеет обходное решение для шаблона в результате работы с несколькими типами фигур - ищите "Unapply". Я не смог использовать его для основного случая (тип типа TC [_], такой как Monoid), хотя он работал на TC [_ [_]] (например, Functor), как шарм, поэтому этот ответ не охватывает этого.

Если кому-то интересно, здесь все нужно в одном фрагменте:

import scala.language.higherKinds

trait Not[A]

object Not {
  implicit def notA[A, TC[_]] = new Not[TC[A]] {}
  implicit def notNotA[A : TC, TC[_]] = new Not[TC[A]] {}

  implicit def notM[M[_], TC[_[_]]] = new Not[TC[M]] {}
  implicit def notNotM[M[_] : TC, TC[_[_]]] = new Not[TC[M]] {}
}

import Not._

type NotNumeric[A] = Not[Numeric[A]]
implicitly[NotNumeric[String]] // succeeds
implicitly[NotNumeric[Int]] // fails

и псевдокод, который я задал в вопросе, будет выглядеть так (фактический код):

// NotFunctor[M[_]] declared above
def notFunctor[M[_] : NotFunctor](m: M[_]) = s"$m is not a functor"

Обновление:. Аналогичная техника применяется к неявным преобразованиям:

import scala.language.higherKinds

trait Not[A]

object Not {
  implicit def not[V[_], A](a: A) = new Not[V[A]] {}
  implicit def notNot[V[_], A <% V[A]](a: A) = new Not[V[A]] {}
}

Теперь мы можем (например) определить функцию, которая будет допускать только значения, если их типы не могут быть просмотрены как упорядоченные:

def unordered[A <% Not[Ordered[A]]](a: A) = a