2013-04-12 5 views
14

tl; dr: come faccio a fare qualcosa di simile al codice formato da qui di seguito:Utilizzando contesto delimita "negativo" per garantire istanza del tipo di classe è assente dal campo di applicazione

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

Il 'Not[Functor]', essendo la parte inventata qui.
Voglio che abbia successo quando la 'm' fornita non è un Functor e altrimenti fallisce il compilatore.

Risolto: salta il resto della domanda e vai avanti alla risposta sotto.


Quello che sto cercando di realizzare è, grosso modo, "prova negativa".

pseudo codice sarebbe simile modo:

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

Tenete a mente che fixedSizeOf() è preferibile se del caso, dal momento che ci salva l'attraversamento sulla raccolta.

In questo modo, per i tipi di container in cui è definito solo Length (ma non Foldable) e per gli elementi in cui è definito un FixedSizeOf, si ottiene un miglioramento delle prestazioni.

Per il resto dei casi, esaminiamo la raccolta e sommiamo le singole dimensioni.

Il mio problema si verifica nei casi in cui sia Length sia Foldable sono definiti per il contenitore e FixedSizeOf è definito per gli elementi. Questo è un caso molto comune qui (ad es., List[Int] ha entrambi definito).

Esempio:

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

Quello che vorrei è quello di poter contare sulla classe Foldable tipo solo quando la combinazione Length + FixedSizeOf non si applica.

A tal fine, posso cambiare la definizione di foldSizeOf() accettare VarSizeOf elementi:

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

E ora dobbiamo compilare la parte problematica che copre Foldable contenitori con FixedSizeOf elementi e non Length definiti . Non sono sicuro di come affrontare questo, ma pseudo-codice sarebbe simile:

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

Il 'Not[Length]', ovviamente, essendo la parte costituita da qui.

soluzioni

parziali Sono a conoscenza di

1) Definire una classe per impliciti bassa priorità ed estenderlo, come si è visto in 'object Predef extends LowPriorityImplicits'. L'ultimo implicito (foldSizeOfFixed()) può essere definito nella classe genitore e verrà sostituito da un'alternativa dalla classe discendente.

Non sono interessato a questa opzione perché mi piacerebbe eventualmente essere in grado di supportare l'uso ricorsivo di SizeOf, e ciò impedirà l'implicito nella classe di base a bassa priorità di fare affidamento su quelli della sottoclasse (è il mio comprensione qui corretto EDIT:?! sbagliato ricerca implicita lavora dal contesto della classe sub, questa è una soluzione praticabile)

2) un approccio più ruvida è basandosi su Option[TypeClass] (ad esempio ,: Option[Length[List]] alcuni di quelli e!. Posso solo scrivere un grande implicito che seleziona Foldable e SizeOf come obbligatorio e Length e FixedSizeOf come facoltativo, e si basa su quest'ultimo se sono disponibili. (Fonte: here)

I due problemi qui sono la mancanza di modularità e ricadere a runtime eccezioni quando non istanze di classe di tipo rilevanti possono essere posizionati (questo esempio può probabilmente essere fatto per lavorare con questa soluzione, ma questo non è sempre possibile)

EDIT: Questo è il meglio che sono riuscito a ottenere con impliciti facoltativi. Non è ancora arrivati:

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

Tranne questo si scontra con fixedSizeOf() da prima, che è ancora necessario.

Grazie per qualsiasi aiuto o prospettiva :-)

+0

Opzione sopra # 1 (quello con priorità implicite) funzionerà. Sono ancora interessato a qualcosa di più elegante, quindi la domanda rimane aperta per ora. – nadavwr

+1

Forse puoi elaborare [il trucco di negazione (miglia di risposta, aggiornamento)] (http://stackoverflow.com/questions/6909053/enforce-type-difference/6944070#6944070) –

+1

Il trucco di negazione potrebbe probabilmente essere fatto funzionare qui ma ritengo che la prioritizzazione sia di gran lunga la soluzione più desiderabile in questa situazione. –

risposta

11

fine ho risolto questo utilizzando una soluzione ambiguità-based che non richiede priorità usare l'ereditarietà.

Ecco il mio tentativo di generalizzare questo.

Usiamo il tipo Not[A] costruire classi tipo negativo:

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

... che può quindi essere utilizzato come contesto limiti:

def foo[T: NotMonoid] = ... 

Si procede facendo in modo che ogni espressione valida Non [A] otterrà almeno un'istanza implicita.

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

L'istanza si chiama 'notA' - 'non' perché se è l'unico caso per 'Non [TC [A]]' allora la classe tipo negativo si trova ad applicare; la "A" viene comunemente aggiunta per i metodi che trattano tipi di forma piatta (ad esempio Int).

Introduciamo ora un'ambiguità di allontanarsi casi in cui il tipo di classe indesiderato è applicata:

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

Questo è quasi esattamente lo stesso come 'Nota', tranne qui ci interessa solo nei tipi di quale un'istanza della classe del tipo specificata da "TC" esiste in ambito implicito. L'istanza si chiama 'notNotA', poiché, semplicemente accoppiando l'essere implicito alzato, creerà un'ambiguità con 'notA', fallendo la ricerca implicita (che è il nostro obiettivo).

Esaminiamo un esempio di utilizzo.Utilizzeremo la classe di tipo negativo "NotMonoid" dall'alto:

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 

Fin qui tutto bene! Tuttavia, i tipi a forma di M [_] e le classi di tipi a forma di TC [_ [_]] non sono ancora supportati dallo schema sopra. Aggiungiamo anche impliciti per loro:

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 

Abbastanza semplice. Notare che Scalaz ha una soluzione alternativa per il boilerplate risultante dall'affrontare diverse forme di tipo: cercare 'Unapply'. Non sono stato in grado di utilizzarlo per il caso base (tipo classe di forma TC [_], come Monoid), anche se ha funzionato su TC [_ [_]] (es. Functor) come un incantesimo, quindi questa risposta non copre questo.

Se qualcuno è interessato, ecco tutto il necessario in un unico frammento:

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 

e il codice pseudo ho chiesto per la questione sarebbe simile modo (codice vero e proprio):

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

Aggiornamento: Tecnica simile applicata alle conversioni implicite:

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]] {} 
} 

Possiamo ora (per esempio) definisce una funzione che non potrà che ammettere valori se i loro tipi non sono visualizzabili come ordinato:

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

La soluzione precedente che ho pubblicato utilizzava il costruttore di tipo binario HasNo [M [\ _], TC [\ _ [\ _]]] (usato infisso: 'Elenco HasNo Functor'), ma espandendo quello per supportare 'A' e 'TC [\ _]' sembrava impossibile senza introdurre un nuovo identificatore. Alla fine ho optato per il tipo "Non [A]" sopra, e ho usato diversi impliciti per annullare la "A" nei suoi componenti (M [\ _] e TC [\ _ [\ _]] o A e TC [\ _]). – nadavwr

+0

Anche se mi piace quando il compilatore rileva la maggior parte degli errori e/o in anticipo decide sui rami dell'algoritmo da prendere, e anche se io sono d'accordo con te su questo sicuramente può essere utile - è ancora una .. caramella. La maggior parte delle persone probabilmente prenderà una strada più facile. Fai attenzione a non essere accusato di http://c2.com/cgi/wiki?MentalMasturbation ;-) Bel lavoro! Continua! ;-) – quetzalcoatl

+1

@quetzalcoatl C'è un badge StackOverflow per questo? – nadavwr

Problemi correlati