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 deSizeOf
e 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 :-)