2015-10-12 3 views
5

I thought Avevo bisogno di parametrizzare la mia funzione su tutti i tipi Ordering[_]. Ma questo non funziona.Come definire una funzione come generica su tutti i numeri in scala?

Come posso far funzionare la seguente funzione per tutti i tipi che supportano le operazioni matematiche necessarie e come ho potuto scoprirlo da solo?

/** 
    * Given a list of positive values and a candidate value, round the candidate value 
    * to the nearest value in the list of buckets. 
    * 
    * @param buckets 
    * @param candidate 
    * @return 
    */ 
    def bucketise(buckets: Seq[Int], candidate: Int): Int = { 

    // x <= y 
    buckets.foldLeft(buckets.head) { (x, y) => 
     val midPoint = (x + y)/2f 

     if (candidate < midPoint) x else y 
    } 
    } 

ho provato il comando facendo clic sugli operatori matematici (/, +) in IntelliJ, ma appena ricevuto un avviso di Sc synthetic function.

risposta

9

Se si desidera utilizzare solo la libreria standard scala, consultare Numeric[T]. Nel tuo caso, dal momento che si desidera eseguire una divisione non intera, è necessario utilizzare la sottoclasse Fractional[T] di Numeric.

Ecco come apparirà il codice utilizzando tipeclass di scala standard. Notare che Fractional si estende da Ordered. Questo è conveniente in questo caso, ma non è matematicamente generico. Per esempio. non è possibile definire un per Complex perché non è ordinato.

def bucketiseScala[T: Fractional](buckets: Seq[T], candidate: T): T = { 
    // so we can use integral operators such as + and/
    import Fractional.Implicits._ 
    // so we can use ordering operators such as <. We do have a Ordering[T] 
    // typeclass instance because Fractional extends Ordered 
    import Ordering.Implicits._ 
    // integral does not provide a simple way to create an integral from an 
    // integer, so this ugly hack 
    val two = (implicitly[Fractional[T]].one + implicitly[Fractional[T]].one) 
    buckets.foldLeft(buckets.head) { (x, y) => 
    val midPoint = (x + y)/two 
    if (candidate < midPoint) x else y 
    } 
} 

Tuttavia, per gravi calcoli numerici generici vorrei suggerire di dare un'occhiata a spire. Fornisce una gerarchia molto più elaborata di esemplari numerici. I typeclass Spire sono anche specializzati e quindi spesso veloci quanto lavorare direttamente con le primitive.

Ecco come utilizzare esempio apparirebbe con guglia:

// imports all operator syntax as well as standard typeclass instances 
import spire.implicits._ 
// we need to provide Order explicitly, since not all fields have an order. 
// E.g. you can define a Field[Complex] even though complex numbers do not 
// have an order. 
def bucketiseSpire[T: Field: Order](buckets: Seq[T], candidate: T): T = { 
    // spire provides a way to get the typeclass instance using the type 
    // (standard practice in all libraries that use typeclasses extensively) 
    // the line below is equivalent to implicitly[Field[T]].fromInt(2) 
    // it also provides a simple way to convert from an integer 
    // operators are all enabled using the spire.implicits._ import 
    val two = Field[T].fromInt(2) 
    buckets.foldLeft(buckets.head) { (x, y) => 
    val midPoint = (x + y)/two 
    if (candidate < midPoint) x else y 
    } 
} 

Spire fornisce anche la conversione automatica da interi a T se esiste un Field[T], così si potrebbe anche scrivere l'esempio come questo (quasi identico a la versione non generica). Tuttavia, penso che l'esempio sopra sia più facile da capire.

// this is how it would look when using all advanced features of spire 
def bucketiseSpireShort[T: Field: Order](buckets: Seq[T], candidate: T): T = { 
    buckets.foldLeft(buckets.head) { (x, y) => 
    val midPoint = (x + y)/2 
    if (candidate < midPoint) x else y 
    } 
} 

Aggiornamento: spire è molto potente e generico, ma può anche essere un po 'di confusione per un principiante. Soprattutto quando le cose non funzionano. Ecco uno excellent blog post che spiega l'approccio di base e alcuni dei problemi.

+0

OK grazie controllerò spire. Il fatto è che lo scala docs per Int (http://www.scala-lang.org/api/current/index.html#scala.Int) non elenca i tratti che estende, quindi ho finito per indovinare su '. Come posso scoprire quali tratti 'Int' si estendono in modo da poter trovare' Numeric [T] 'per me stesso? – jbrown

+0

@jbrown L'ordine è un [typeclass] (http://danielwestheide.com/blog/2013/02/06/the-neophytes-guide-to-scala-part-12-type-classes.html). I typeclasses non funzionano con l'ereditarietà. –

+0

Quindi, come avrebbe potuto sapere, guardando i documenti, quali tipi di lettere si applicano a Int? – jbrown