Użycie granic kontekstu „negatywnie” w celu zapewnienia, że instancja klasy typu jest nieobecna w zakresie
tl; dr: Jak zrobić coś takiego, jak poniższy kod:
def notFunctor[M[_] : Not[Functor]](m: M[_]) = s"$m is not a functor"
The 'Not[Functor]
„, będąc tutaj częścią.
Chcę, żeby się powiodło, gdy dostarczone „m” nie jest Funktorem, a kompilator zawiedzie inaczej.
Rozwiązany: pomiń resztę pytania i przejdź do odpowiedzi poniżej.
To, co staram się osiągnąć, to z grubsza „negatywne dowody”.
Pseudo kod wyglądałby mniej więcej tak:
// 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))
}
Weź pod uwagę, żefixedSizeOf()
jest preferowany tam, gdzie jest to istotne, ponieważ oszczędza nam przejścia przez kolekcję.
W ten sposób dla typów kontenerów, gdzie tylkoLength
jest zdefiniowany (ale nieFoldable
), a dla elementów, gdzieFixedSizeOf
jest zdefiniowany, uzyskujemy lepszą wydajność.
W pozostałych przypadkach przeglądamy kolekcję i sumujemy poszczególne rozmiary.
Mój problem dotyczy przypadków, w których obaLength
iFoldable
są zdefiniowane dla kontenera iFixedSizeOf
jest zdefiniowany dla elementów. Jest to bardzo częsty przypadek tutaj (np.:List[Int]
ma oba zdefiniowane).
Przykład:
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))
Chciałbym móc polegać naFoldable
typ klasy tylko wtedy, gdyLength
+FixedSizeOf
połączenie nie ma zastosowania.
W tym celu mogę zmienić definicjęfoldSizeOf()
akceptowaćVarSizeOf
elementy:
implicit def foldSizeOfVar[T[_] : Foldable, A : VarSizeOf] = // ...
A teraz musimy wypełnić problematyczną część, która obejmujeFoldable
pojemniki zFixedSizeOf
elementy iNieLength
zdefiniowane. Nie jestem pewien, jak podejść do tego, ale pseudo kod wygląda tak:
implicit def foldSizeOfFixed[T[_] : Foldable : Not[Length], A : FixedSizeOf] = // ...
The 'Not[Length]
„oczywiście, będąc tutaj częścią.
Częściowe rozwiązania, o których wiem
1) Zdefiniuj klasę dla implikacji o niskim priorytecie i rozszerz ją, jak widać w 'object Predef extends LowPriorityImplicits
” Ostatni ukryty (foldSizeOfFixed()
) może być zdefiniowany w klasie nadrzędnej i zostanie zastąpiony przez alternatywę z klasy potomnej.
Nie interesuje mnie ta opcja, ponieważ chciałbym w końcu móc obsługiwać rekurencyjne użycieSizeOf
, a to uniemożliwi domniemanie w klasie bazowej o niskim priorytecie polegania na tych w podklasie (czy moje zrozumienie tutaj jest poprawne? EDIT: błędne! niejawne wyszukiwanie działa z kontekstu podklasy, jest to realne rozwiązanie!)
2) Od tego zależy bardziej surowe podejścieOption[TypeClass]
(na przykład.,:Option[Length[List]]
. Kilka z nich i mogę po prostu napisać jeden wielki olbrzymi, który wybieraFoldable
iSizeOf
jako obowiązkowe iLength
iFixedSizeOf
jako opcjonalne i opiera się na tym ostatnim, jeśli są dostępne. (źródło:tutaj)
Dwa problemy w tym przypadku to brak modularności i powrót do wyjątków czasu wykonywania, gdy nie można zlokalizować odpowiednich instancji klasy typów (ten przykład można prawdopodobnie zastosować do tego rozwiązania, ale nie zawsze jest to możliwe)
EDYCJA: To jest najlepsze, jakie udało mi się uzyskać dzięki opcjonalnym implikacjom. Jeszcze go tam nie ma:
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))
}
}
}
Z wyjątkiem tego, z którym się zderzafixedSizeOf()
z wcześniejszej, która nadal jest konieczna.
Dziękuję za wszelką pomoc lub perspektywę :-)