Usando limites de contexto “negativamente” para garantir que a instância de classe de tipo esteja ausente do escopo

tl; dr: Como faço algo como o código inventado abaixo:

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

O 'Not[Functor]', sendo a parte feita aqui.
Eu quero que ele seja bem-sucedido quando o 'm' fornecido não for um Functor e falhe o compilador de outra forma.

Resolvido: pule o resto da pergunta e vá em frente para a resposta abaixo.

O que estou tentando realizar é, grosso modo, "evidência negativa".

O pseudocódigo seria parecido com:

// 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))
}

Tenha em mente quefixedSizeOf() é preferível quando relevante, já que nos salva a travessia da coleção.

Desta forma, para tipos de contêiner onde somenteLength está definido (mas nãoFoldable) e para elementos em queFixedSizeOf é definido, obtemos um melhor desempenho.

Para o resto dos casos, revisamos a coleta e somamos os tamanhos individuais.

Meu problema é nos casos em que ambosLength eFoldable são definidos para o contêiner eFixedSizeOf é definido para os elementos. Este é um caso muito comum aqui (por exemplo,:List[Int] tem ambos definidos).

Exemplo:

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))

O que eu gostaria é poder contar com oFoldable digite classe somente quando oLength+FixedSizeOf combinação não se aplica.

Para isso, posso mudar a definição defoldSizeOf() aceitarVarSizeOf elementos:

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

E agora temos que preencher a parte problemática que cobreFoldable recipientes comFixedSizeOf elementos enãoLength definiram. Não sei como abordar isso, mas o pseudo-código seria algo como:

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

O 'Not[Length]', obviamente, sendo a parte inventada aqui.

Soluções parciais que conheço

1) Defina uma classe para baixa prioridade implícita e estenda-a, como visto em 'object Predef extends LowPriorityImplicits'. O último implícito (foldSizeOfFixed()) pode ser definido na classe pai e será substituído pela alternativa da classe descendente.

Não estou interessado nesta opção porque gostaria de poder, eventualmente, suportar o uso recursivo deSizeOfe isso evitará que o implícito na classe base de baixa prioridade confie naqueles da subclasse (o meu entendimento aqui está correto? EDIT: errado! pesquisa implícita funciona a partir do contexto da subclasse, essa é uma solução viável!)

2) Uma abordagem mais difícil é confiarOption[TypeClass] (por exemplo.,:Option[Length[List]]. Alguns desses e eu posso apenas escrever um grande e implícito que escolheFoldable eSizeOf como obrigatório eLength eFixedSizeOf como opcional, e depende do último, se estiverem disponíveis. (fonte:Aqui)

Os dois problemas aqui são falta de modularidade e retorno às exceções de tempo de execução quando nenhuma instância de classe de tipo relevante pode ser localizada (este exemplo provavelmente pode funcionar com essa solução, mas isso nem sempre é possível)

EDIT: Este é o melhor que eu consegui obter com implícitos opcionais. Ainda não está lá:

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))
    }
  }
}

Exceto isso colide comfixedSizeOf() de antes, o que ainda é necessário.

Obrigado por qualquer ajuda ou perspectiva :-)

questionAnswers(1)

yourAnswerToTheQuestion