2015-12-05 17 views
10

Czy mogę używać dopasowywania wzorców do bezkształtnych produktów ubocznych?Dopasowywanie wzorców z bezkształtnym współproduktem

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 
} 

To oczywiście nie zadziała, ponieważ a jest zapakowane jako Coproduct.

Czy istnieje alternatywny sposób wykorzystania produktów ubocznych i utrzymanie dopasowania wzoru?

Odpowiedz

14

Można użyć Inl i Inr konstruktorów w meczu Wzór:

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 
} 

Podejście to nie jest idealne, bo trzeba sobie sprawę CNil jeśli chcesz kompilator, aby móc powiedzieć, że mecz jest wyczerpująca, wiemy, że to nie jest możliwe, że sprawa się zgadzają, ale kompilator nie robi, więc musimy zrobić coś takiego:

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

ja również osobiście po prostu znaleźć, przechodząc do APPro Właściwe pozycje w współprodukcie z Inr i Inl są trochę sprzeczne z intuicją.

Generalnie lepiej spasować nad koproduktu z polimorficznych wartości funkcji:

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) 

Teraz kompilator będzie weryfikacji kompletności bez konieczności obsługi niemożliwych przypadki.

+0

ja usiłuję zrozumieć co 'przypadek INL (0 :: sekund :: Nil) => second' rzeczywistości. Jeśli jest to współprodukt, na pewno nie może to być zarówno "0", jak i "sekunda", więc kiedy to się zgadza? – fommil

+0

@fommil Część wewnątrz 'Inl' pasuje do zwykłej starej listy, więc będzie pasować za każdym razem, gdy mamy' Coproduct [ListOrString] (xs) 'gdzie' xs' ma dokładnie dwa elementy i '0' jest pierwszy. –

+0

oh Widzę, dzięki – fommil

4

Właśnie przesłałem Shapeless żądanie pobrania here, które może działać dobrze dla twoich potrzeb. (Zauważ, że jest to tylko żądanie pobrania i może zostać zmienione lub odrzucone ... ale nie krępuj się zabrać maszyn i użyć go we własnym kodzie, jeśli uznasz to za użyteczne.)

Z komunikatu o zatwierdzeniu:

[...] przez współprodukt c typu Int: +: Ciąg: +: logiczna: +: CNIL mogą być składane w podwójną następująco:

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

ten zapewnia pewne korzyści od exi metody sting do składania ponad Produktów koprodukcyjnych. W przeciwieństwie do klasy typu Folder, ten nie wymaga funkcji polimorficznej o stabilnym identyfikatorze, więc składnia jest nieco lekka i lepiej pasuje do sytuacji, w których funkcja składania nie jest ponownie wykorzystywana (np., Kombinatorowe biblioteki parserów).

Dodatkowo, w przeciwieństwie do bezpośredniego składania na produkcie Copproduct z wzorcem dopasowanie wtryskiwaczy Inl i Inr, klasa tego typu gwarantuje, że wynikowy krotek jest wyczerpujący. Możliwe jest również częściowe złożenie zamówienia (tak długo jak sprawy są obsługiwane w podanej kolejności przez podpis typu Koprodukt), co umożliwia stopniowe składanie Koproduktu.

Dla przykładu, można to zrobić:

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) 
Powiązane problemy