2015-06-07 14 views
6

Continuo a esaminare record estensibili come in Passing a Shapeless Extensible Record to a Function (continued): la soluzione fornita funziona con funzioni che prendono tutti un parametro che include almeno foo1, foo2 e foo3; si può scrivere:Passare un record estendibile informe a una funzione (storia infinita?

fun1(("foo1" ->> "hello") :: ("foo2" ->> 1) :: ("foo3" ->> 1.2) :: HNil) 
fun1(("foo1" ->> "hello") :: ("foo2" ->> 1) :: ("foo3" ->> 1.2) :: ("foo4" ->> true) :: HNil) 

e così via

e se siamo in grado di aggiungere una seconda funzione fun2:

def fun2[L <: HList : HasMyFields](xs: L) = { 
    val selectors = implicitly[HasMyFields[L]] 
    import selectors._ 
    xs("foo1").length + xs("foo2") + xs("foo3") 
} 

Fin qui, tutto bene.

Ora, supponiamo di avere un set di campi foo1, foo2, ... E un set di funzioni fun1, fun2, che prendono come parametro un record composto con qualsiasi sottoinsieme di {foo1, foo2, ...} . Ad esempio, fun1 potrebbe prendere come parametro un record che contiene foo1 e foo3 ma non necessariamente foo2, fun2 si aspetta un record con almeno foo4 e così via.

C'è un modo per evitare di dichiarare tutte le classi come HasMyFields in quanto sono possibili combinazioni (se abbiamo n campi, ci sono 2 ** n combinazioni!)?

risposta

6

Questo è molto più facile, senza una classe di tipo supplementare con il nuovo-ish Witness sintassi:

import shapeless._, ops.record.Selector, record._, syntax.singleton._ 

// Uses "foo1" and "foo2" fields; note that we get the appropriate static types. 
def fun1[L <: HList](l: L)(implicit 
    foo1: Selector.Aux[L, Witness.`"foo1"`.T, String], 
    foo2: Selector.Aux[L, Witness.`"foo2"`.T, Int] 
): (String, Double) = (foo1(l), foo2(l)) 

E poi se abbiamo questo disco:

val rec = ("foo1" ->> "hello") :: ("foo2" ->> 1) :: ("foo3" ->> 1.2) :: HNil 

otteniamo questo:

scala> fun1(rec) 
res0: (String, Double) = (hello,1.0) 

La nuova sintassi ci consente di evitare di creare valori di test separatamente, quindi è pr È facile richiedere solo le istanze Selector necessarie.

+0

sei infinitamente inestimabile! Molte grazie! – bhericher

2

partire con la risposta di Travis, sono stato in grado di migliorarlo:

In un primo file, ho:

package p2; 
import shapeless._, ops.record.Selector, record._, syntax.singleton._ 

object MyFields { 

    type wfoo1[L<: HList]=Selector.Aux[L,Witness.`"foo1"`.T, String] 

    type wfoo2[L<: HList]=Selector.Aux[L,Witness.`"foo2"`.T, Int] 
} 

e poi, altrove:

package p1; 
import shapeless._, ops.record.Selector, record._, syntax.singleton._ 
import p2.MyFields._ 
object testshapeless extends App { 

    def fun1[L <: HList](l: L)(implicit 
    foo1: wfoo1[L], 
    foo2: wfoo2[L] 
): (String, Double) = (foo1(l), foo2(l)) 

    val rec = ("foo1" ->> "hello") :: ("foo2" ->> 1) :: ("foo3" ->> 1.2) :: HNil 

    println(fun1(rec)); 
} 

Il primo file può essere considerato come uno schema in cui dichiaro i campi che potenzialmente utilizzo nella mia applicazione e devo solo importarlo.

Questo è fantastico!

Redatta il 30 giugno: Mi chiedo se si poteva fare di meglio, magari con una macro: Sarebbe possibile scrivere qualcosa di simile:

def fun1[L <:HList] WithSelectors(MyFields)=... 

I WithSelectors macro genererebbero:

(implicit foo1: wfoo1[L], foo2: wfoo2[L]) 

Qualche consiglio?

Problemi correlati