2015-12-05 15 views
10

È possibile utilizzare la corrispondenza di modelli con coprodotti senza forma?Corrispondenza modello con coproduzione senza forma

import shapeless.{CNil, :+:} 

type ListOrString = List[Int] :+: String :+: CNil 

def f(a: ListOrString): Int = a match { 
    case 0 :: second :: Nil => second 
    case first :: Nil => first 
    case Nil => -1 
    case string: String => string.toInt 
} 

Che ovviamente non funziona, dal momento che a viene inscatolato come Coproduct.

Esiste un modo alternativo per utilizzare i coprodotti e mantenere la capacità di abbinare i modelli?

risposta

14

È possibile utilizzare i Inl e Inr costruttori nel match modello:

import shapeless.{ CNil, Inl, Inr, :+: } 

type ListOrString = List[Int] :+: String :+: CNil 

def f(a: ListOrString): Int = a match { 
    case Inl(0 :: second :: Nil) => second 
    case Inl(first :: Nil) => first 
    case Inl(Nil) => -1 
    case Inr(Inl(string)) => string.toInt 
} 

Questo approccio non è l'ideale perché si deve gestire il caso CNil se si desidera che il compilatore di essere in grado di dire che la partita è esaustivo, sappiamo che non è possibile per quel caso per abbinare, ma il compilatore non lo fa, quindi dobbiamo fare qualcosa di simile:

def f(a: ListOrString): Int = a match { 
    case Inl(0 :: second :: Nil) => second 
    case Inl(first :: Nil) => first 
    case Inl(Nil) => -1 
    case Inl(other) => other.sum 
    case Inr(Inl(string)) => string.toInt 
    case Inr(Inr(_)) => sys.error("Impossible") 
} 

ho anche personalmente appena trovato la navigazione verso l'appro posizioni corte nel coprodotto con Inr e Inl un po 'controintuitivo.

In generale è meglio ripiegare il coprodotto con un valore della funzione polimorfica:

object losToInt extends shapeless.Poly1 { 
    implicit val atList: Case.Aux[List[Int], Int] = at { 
    case 0 :: second :: Nil => second 
    case first :: Nil => first 
    case Nil => -1 
    case other => other.sum 
    } 

    implicit val atString: Case.Aux[String, Int] = at(_.toInt) 
} 

def f(a: ListOrString): Int = a.fold(losToInt) 

Ora il compilatore verificherà esaustività senza dover per gestire i casi impossibili.

+0

faccio fatica a capire che cosa 'caso Inl (0 :: :: secondo Nil) => secondo' in realtà è. Se questo è un coprodotto, sicuramente non può essere sia "0" che "secondo", quindi quando si fa questo match? – fommil

+0

@fommil La parte all'interno di 'Inl' corrisponde a una semplice vecchia lista, quindi corrisponderà ogni volta che abbiamo un' Coproduct [ListOrString] (xs) 'dove' xs' ha esattamente due elementi e '0' è il primo. –

+0

oh capisco, grazie – fommil

4

Ho appena inviato a Shapeless una richiesta pull here che potrebbe funzionare bene per le vostre esigenze. (Si noti che è solo una richiesta pull e può subire revisioni o essere respinta ... ma sentirsi libero di prendere il macchinario e usarlo nel proprio codice se lo trova utile.)

Dal messaggio di commit:

[...] coprodotto c di tipo Int: +: String: +: booleana: +: cnil potrebbe essere ripiegato a doppio come segue:

val result = c.foldCases[Double] 
       .atCase(i => math.sqrt(i)) 
       .atCase(s => s.length.toDouble) 
       .atCase(b => if (b) 100.0 else -1.0) 

Questo fornisce alcuni vantaggi rispetto a exi metodi di puntura per piegare sopra Coprodotti. A differenza della classe di tipo Cartella, questa non richiede una funzione polimorfica con un identificatore stabile, pertanto la sintassi è un po 'leggera e più adatta alle situazioni in cui la funzione di piegatura non viene riutilizzata (ad esempio, librerie combinatore di parser).

Inoltre, a differenza di piegatura direttamente su una coprodotto con pattern corrispondenza sopra INL e INR iniettori, questo tipo garantisce classe che la piega risultante è esaustivo. È anche possibile aggiungere parzialmente un Coprodotto (a condizione che i casi vengano gestiti nell'ordine specificato mediante la firma del tipo di prodotto Coproduct), che consente di aggiungere un Coprodotto in modo incrementale a .

Per esempio, si potrebbe fare questo:

def f(a: ListOrString): Int = a.foldCases[Int] 
    .atCase(list => list match { 
     case 0 :: second :: Nil => second 
     case first :: Nil => first 
     case Nil => -1 
     case other => other.sum 
    }) 
    .atCase(s => s.toInt) 
Problemi correlati