2015-09-16 12 views
5

Biorąc pod uwagę następujące AST dla Success i Failure:ograniczające Rodzaj Podpisy dla lewego i prawego

sealed trait Success 
case object FooGood extends Success 
case object BarGood extends Success 

sealed trait Failure 
case object FooBad extends Failure 
case object BarBad extends Failure 

oraz podpis metoda:

def go[A <: Failure, B <: Success](x: Int): Either[A, B] = ??? 

Jednak chciałbym, aby ograniczyć Left i Right typy specyficzne dla Foo lub Bar.

ale następujący kod kompiluje (wbrew mojej woli):

scala> go[FooBad.type, BarGood.type](5) 
scala.NotImplementedError: an implementation is missing 

Jak mogę osiągnąć to ograniczenie w czasie kompilacji?

+1

Dlaczego nie używając cechę Foo i bar, z tą definicją: def go [F] (x: Int): Albo [Awaria F, Sukces F] = ??? – volia17

+0

Zgadzam się, że powinien się skompilować. Nie twierdziłem, że powinno ** nie ** kompilować. Moje pytanie brzmi: jak napisać implementację 'go' (w sposób ogólny), której nie uda się skompilować, jeśli' Bar' nie pojawi się po obu stronach, tj. 'Right' i' Left' lub odwrotnie dla 'right' Foo'. –

Odpowiedz

-1

Jeśli istnieje wspólny super typ chcesz ograniczyć zarówno A & B, można wykorzystać Scala Compound Types następująco:

def go[F, A <: Failure with F, B <: Success with F](x: Int): Either[A, B] = ??? 

Gdzie F jest wspólnym super typ zarówno A i B .

+0

def go [F, A <: Niepowodzenie z F, B <: Powodzenie z F] (x: Int): Albo [A, B] = ???, jeśli chcesz iść bardziej ogólny – volia17

+0

Dlaczego spadki? – Chris

+0

@ volia17 zgodził się, to o wiele milszy podpis – Chris

2

Problem polega na tym, że kompilator nie wie, że FooGood jest w jakiś sposób powiązany z FooBad, więc musisz go jakoś wskazać.

Oto co wymyśliłem, chociaż muszę przyznać, że nie jest bardzo elegancki:

trait Grouping[B, G] 

object FooHelper { 
    implicit object fooGrouping Grouping[FooBad.type, FooGood.type] 
} 

object BarHelper { 
    implicit object barGrouping Grouping[BarBad.type, BarGood.type] 
} 

def go[A <: Failure, B <: Success](x: Int)(implicit ev: Grouping[A, B]): Either[A, B] = ??? 


import FooHelper._ 
import BarHelper._ 

// the following two type check 
go[FooBad.type, FooGood.type](5) 
go[BarBad.type, BarGood.type](5) 

// while these two do not 
go[FooBad.type, BarGood.type](5) 
go[BarBad.type, FooGood.type](5) 

Jak widać wskazówka jest realizowany poprzez stworzenie Grouping i oddanie poprawne zgrupowania w zakresie niejawny. Problem z tym podejściem polega na tym, że użytkownik może tworzyć własne grupy, które mogą nie być prawidłowe.

0

Jest możliwe, aby ograniczyć typ wyjścia za parę Success/Failure wprowadzając parametr typu:

sealed trait Success[T] 
sealed trait Failure[T] 

sealed trait Foo 
sealed trait Bar 

case object FooGood extends Success[Foo] 
case object BarGood extends Success[Bar] 

case object FooBad extends Failure[Foo] 
case object BarBad extends Failure[Bar] 

def go[T, A <: Failure[T], B <: Success[T]](x: Int): Either[A, B] = ??? 

Ale! Ma to sens tylko wtedy, gdy utworzysz pewną konkretną implementację, która zapewnia typ T, ponieważ w inny sposób tworzenie takiej sygnatury jest bez znaczenia.

widzę żadnego narzędzia do tego, jako część interfejsu, który ogranicza wynik go, tak:

trait Paired[T] {     
    type A <: Failure[T]    
    type B <: Success[T]    
    def go(x: Int): Either[A, B]  
} 

Jeśli chcesz dostarczyć rodzajów jawnie (ale w jaki sposób można stworzyć implementację do tego?) - odpowiedź z zapewnieniem niejawnego grupowania jest bardzo rozsądna.

+0

Dodawanie: 'zapieczętowana cecha FooBarParent' (które rozszerzają' Foo' i 'Bar'), a następnie:' def go [T <: FooBarParent, ... reszta twojego kodu ... 'dodaje to ograniczenie? –

+1

@ KevinMeredith Logicznie zużywałoby to dowolny 'FooBarParent'. Przy okazji, jeśli wyjaśnisz w pytaniu, w jaki sposób zamierzasz użyć swojego kodu, może to doprowadzić ludzi do lepszych propozycji. Nawet jeśli zdarzy ci się zadeklarować jakiś podpis, który spełnia ciebie w jednej metodzie - nie możesz dać mu żadnej konkretnej implementacji, z wyjątkiem '???' lub innego wyjątku. Jaki jest przypadek użycia? – dmitry

4

Oto rozwiązanie, które opiera się na klasie typów. Należy zauważyć, że nie wymaga to ręcznego definiowania instancji nowej klasy dla każdego węzła (pary) AST. Wymaga to wprowadzenia wspólnego supertypu dla każdej pary (choć technicznie nie jest to możliwe, aby miał używać go jako klasy bazowej, jest używany jedynie jako typ znacznika).Test

sealed trait Success[T] 
abstract sealed class Foo 
abstract sealed class Bar 
case object FooGood extends Foo with Success[Foo] 
case object BarGood extends Bar with Success[Bar] 
sealed trait Failure[T] 
case object FooBad extends Foo with Failure[Foo] 
case object BarBad extends Bar with Failure[Bar] 

@annotation.implicitNotFound("Expecting reciprocal Failure and Success alternatives, but got ${A} and ${B}") 
trait IsGoodAndBadFacet[A,B] 
implicit def isGoodAndBadFacet[T,A,B](implicit e1: A <:< Failure[T], e2: B<:<Success[T]): IsGoodAndBadFacet[A,B] = null 

def go[A, B](x: Int)(implicit e: IsGoodAndBadFacet[A,B]): Either[A, B] = ??? 

Repl:

scala> go[FooBad.type, BarGood.type](5) 
<console>:17: error: Expecting reciprocal Failure and Success alternatives, but got FooBad.type and BarGood.type 
       go[FooBad.type, BarGood.type](5) 
             ^

scala> go[FooBad.type, FooGood.type](5) 
scala.NotImplementedError: an implementation is missing 
    at scala.Predef$.$qmark$qmark$qmark(Predef.scala:225) 
    at .go(<console>:11) 
    ... 33 elided 

scala> go[BarBad.type, BarGood.type](5) 
scala.NotImplementedError: an implementation is missing 
    at scala.Predef$.$qmark$qmark$qmark(Predef.scala:225) 
    at .go(<console>:11) 
    ... 33 elided 
Powiązane problemy