2015-04-10 11 views
8

mam jakiś kod tak:Jak mogę napisać funkcję ma polimorficzny typ zwracania oparty na argumencie typu tego typu?

sealed trait Foo[A] { 
    def value: A 
} 
case class StringFoo(value: String) extends Foo[String] 
case class IntFoo(value: Int) extends Foo[Int] 

Chciałbym mieć funkcję, która może używać rodzaju A dany typ parametru podtypem jest.

// Hypothetical invocation 
val i: Int = dostuff[IntFoo](param) 
val s: String = dostuff[StringFoo](param) 

nie mogę dowiedzieć się, w jaki sposób zadeklarować dostuff w sposób, który działa. Najbliższy rzeczą, jaką mogę wymyślić jest

def dostuff[B <: Foo[A]](p: Param): A 

Ale to nie działa, ponieważ A jest niezdefiniowane w tej pozycji. Mogę zrobić coś

def dostuff[A, B <: Foo[A]](p: Param): A 

Ale potem muszę powołać go jak dostuff[String, StringFoo](param) który jest dość brzydki.

Wydaje się, że kompilator powinien mieć wszystkie informacje potrzebne do poruszania A zmierzającej do zwracanego typu, w jaki sposób można dokonać tej pracy, zarówno w standardowej Scala lub z biblioteką. Jestem na scala 2.10 obecnie, jeśli to wpływa na odpowiedź. Jestem otwarty na 2,11-jedynym rozwiązaniem, jeśli jest to możliwe tam, ale niemożliwe 2.10

Odpowiedz

6

Jak zapewne wiesz, jeśli masz parametr typu Foo[A], to może sprawić, że metody rodzajowe w zaledwie A:

def dostuff[A](p: Foo[A]): A = ??? 

Ponieważ nie zawsze tak jest, możemy spróbować użyć niejawnego parametru, który może wyrażać relację między A a B. Ponieważ możemy nie tylko zastosować niektóre ogólne parametry do wywołania metody (ogólne wnioskowanie parametru jest całkowite lub nic), musimy podzielić to na 2 wywołania. Jest to opcja:

case class Stuff[B <: Foo[_]]() { 
    def get[A](p: Param)(implicit ev: B => Foo[A]): A = ??? 
} 

Można sprawdzić typy w REPL:

:t Stuff[IntFoo].get(new Param) //Int 
:t Stuff[StringFoo].get(new Param) //String 

Innym rozwiązaniem według tych samych zasad, ale przy użyciu anonimową klasę, jest:

def stuff[B <: Foo[_]] = new { 
    def apply[A](p: Param)(implicit ev: B <:< Foo[A]): A = ??? 
} 

:t stuff[IntFoo](new Param) //Int 

Tutaj użyłem apply zamiast get, więc możesz zastosować tę metodę bardziej naturalnie. Również, jak zasugerowałem w twoim komentarzu, tutaj użyłem <:< w typie dowodów. Dla tych, którzy chcą dowiedzieć się więcej na temat tego typu ogólnego ograniczenia typu, można przeczytać więcej here.

Można również rozważyć użycie tutaj członów typu abstrakcyjnego zamiast ogólnych. Kiedy zmaga się z ogólnym wnioskiem, często zapewnia to eleganckie rozwiązanie. Możesz przeczytać więcej o członkach abstrakcyjnych i ich związku z generycznymi here.

+0

Tak właśnie poszedłem, ale zamiast 'ev: B => Foo [A]' użyłem 'ev: B <: Daenyth

5

Inną opcją jest użycie członków typ:

sealed trait Foo { 
    type Value 
    def value: Value 
} 

case class StringFoo(value: String) extends Foo { type Value = String } 
case class IntFoo(value: Int) extends Foo { type Value = Int } 

def dostuff[B <: Foo](p: Any): B#Value = ??? 

// Hypothetical invocation 
val i: Int = dostuff[IntFoo](param) 
val s: String = dostuff[StringFoo](param) 

Zauważ, że oba rozwiązania pracują głównie wokół składniowej ograniczeń w Scala, że ​​nie można ustalić jeden parametr typu wykazu i mieć kompilator wywnioskować drugiej.

+0

Jest to w rzeczywistości * prawidłowe rozwiązanie. Ponieważ autor pytania chce uzyskać typ powiązany z innym typem, zakłada się domyślnie, że taka relacja jest unikalna - dla każdego typu źródła istnieje jeden typ celu. To jest właśnie to, do czego należą członkowie typu abstrakcyjnego/typy powiązane. –

+0

Próbowałem tego, ale dla mojego konkretnego przypadku otrzymywałem błędy, które wyglądały tak, jakby wykonywały pewne zależne od ścieżki pisanie, z wartością jednego StringFoo nie równego wartości innego – Daenyth

Powiązane problemy