2015-06-24 13 views
5

Sto provando a usare "copiato" senza forma per l'aggregazione dei tipi di errore. Quanto segue è un tentativo di isolare il problema ho attualmente:C'è un modo per estendere le dichiarazioni di tipo?

import shapeless._ 

case object F1 
case object F2 
type F12 = F1.type :+: F2.type :+: CNil 

case object F3 
case object F4 
type F34 = F3.type :+: F4.type :+: CNil 

type F1234 = F1.type :+: F2.type :+: F3.type :+: F4.type :+: CNil 

def custom(f: Either[F12, F34]): F1234 = // how can I declare the resulting type? 
    f.fold(_.extendRightBy[F34], _.extendLeftBy[F12]) 

object F1234Handler extends Poly1 { 
    implicit def caseF1 = at[F1.type](_ => "got F1") 
    implicit def caseF2 = at[F2.type](_ => "got F2") 
    implicit def caseF3 = at[F3.type](_ => "got F3") 
    implicit def caseF4 = at[F4.type](_ => "got F4") 
} 

custom(Left(Coproduct[F12](F2))).fold(F1234Handler) // got F2 

Come posso dichiarare il tipo di risultato nella piega personalizzato senza dover ripetere me stesso? Idealmente non voglio dichiarare F1234 come ho fatto, voglio dichiararlo semplicemente facendo una unione delle due dichiarazioni di tipo esistenti, F12 e F34. In questo modo non ho bisogno di aggiornare la dichiarazione F1234 ogni volta che aggiungo un altro tipo di errore a una di queste dichiarazioni. Posso dichiarare il tipo F1234 = F1.type: +: F2.type: +: F34 ma non posso dichiarare il tipo F1234 = F12: +: F34 a causa della coda CNil di F12, che viene rilasciata dalle operazioni extendBy.

risposta

5

La situazione non è così grave come suggerisce la risposta di lmm, in parte perché Shapeless fornisce una classe di tipo ExtendBy che fa i pacchetti su ExtendLeftBy e ExtendRightBy. Quindi, se si voleva davvero un tipo di ritorno per custom che non hai calcolare da soli e scrivere a mano, si potrebbe usare ExtendBy:

import shapeless._, ops.coproduct.ExtendBy 

case object F1 
case object F2 
type F12 = F1.type :+: F2.type :+: CNil 

case object F3 
case object F4 
type F34 = F3.type :+: F4.type :+: CNil 

def custom(f: Either[F12, F34])(implicit ext: ExtendBy[F12, F34]): ext.Out = 
    f.fold(ext.right(_), ext.left(_)) 

Anche se avete bisogno di usare ExtendLeftBy e ExtendRightBy direttamente, si potrebbe convincere il compilatore che ha lo stesso tipo di output un po 'più pulito con i tipi Aux e un singolo parametro di tipo condiviso. Così, invece di questo (una versione funzionante completa di codice di LMM):

import ops.coproduct.{ ExtendLeftBy, ExtendRightBy } 

def custom[ERO, ELO](f: Either[F12, F34])(implicit 
    el: ExtendRightBy[F12, F34] { type Out = ELO }, 
    er: ExtendLeftBy[F12, F34] { type Out = ERO }, 
    w: ELO =:= ERO 
): ERO = f.fold(l => w(el(l)), er(_)) 

si sarebbe solo scrivere questo:

def custom[Out <: Coproduct](f: Either[F12, F34])(implicit 
    extL: ExtendRightBy.Aux[F12, F34, Out], 
    extR: ExtendLeftBy.Aux[F12, F34, Out] 
): Out = f.fold(extL(_), extR(_)) 

Nella maggior parte dei casi, se si conoscono i tipi di ingresso in modo statico, però, che ci si basta scrivere il tipo di ritorno da soli e saltare del tutto il business dei parametri impliciti. La prova implicita è necessario solo quando si lavora con i tipi generici, come questo:

def custom[A <: Coproduct, B <: Coproduct](f: Either[A, B])(implicit 
    ext: ExtendBy[A, B] 
): ext.Out = f.fold(ext.right(_), ext.left(_)) 

Questo funziona per qualsiasi due coprodotti, non solo F12 e F34.

+0

Ciao Travis, grazie per la tua risposta. Avevo individuato Extend ma non riuscivo a capire come farlo funzionare. – sparkle

3

I tipi dipendenti dalla scala sono sempre un po 'strani e ingombranti. Le funzioni a livello di tipo sono codificate come implicite con i risultati come membri di tipo, ma per quanto riguarda il compilatore di scala è solo un membro di tipo. Quindi devi fare qualcosa di simile:

def custom(f: Either[F12, F34])(implicit er: ExtendRight[F12, F34]): er.Out 
    = f.fold(_.extendRightBy[F34], _.extendLeftBy[F12]) 

Purtroppo questo non funzionerà perché il compilatore non può dire che l'uscita del extendLeftBy[F12] è dello stesso tipo. Sappiamo che i due tipi saranno sempre gli stessi, ma il compilatore no, quindi dobbiamo richiedere un testimone (che sarà sempre presente). Qualcosa di simile:

def custom(f: Either[F12, F34])(implicit er: ExtendRight[F12, F34], 
    el: ExtendLeft[F34, F12])(implicit w: er.Out =:= el.Out): er.Out 
    = f.fold(_.extendRightBy[F34], w(_.extendLeftBy[F12])) 

Purtroppo anche questo non funziona perché i nostri parametri di tipo non sono autorizzati a dipendere da parametri di tipo appartenente alla medesima lista e possiamo avere una sola lista implicita. Quindi dobbiamo "lift" questi tipi di essere parametri di tipo invece:

def custom[ERO, ELO](f: Either[F12, F34])(
    implicit er: ExtendRight[F12, F34]{type Out = ERO}, 
    el: ExtendLeft[F34, F12]{type Out = ELO}, w: ELO =:= ERO): ELO 
    = f.fold(_.extendRightBy[F34], w(_.extendLeftBy[F12])) 

Come ho detto, ingombrante, ma dovrebbe funzionare. (ExtendRight e ExtendLeft sono i tipi utilizzati dal metodo extendRightBy e extendLeftBy - qualsiasi funzione tipizzata in maniera simile probabilmente avrà tipi "helper" simili).

+0

Spero che non ti dispiaccia che ho aggiunto la mia risposta dato che i miei suggerimenti non si adattavano a un commento. –

+0

Niente affatto - dovremmo ottenere la migliore risposta possibile. Non so nemmeno informe quanto te, ma certamente avrei dovuto notare che potevamo usare un singolo parametro di tipo 'Out' ed evitare di aver bisogno di' =: = '. ('Aux' è solo un alias IIRC). – lmm

+0

Ciao Imm, grazie per la tua risposta! Spero non ti dispiaccia che ho eletto la risposta di Travis, entrambi spieghi molto bene il problema. – sparkle

Problemi correlati