2013-01-19 8 views
8

Oczywiście zdaję sobie sprawę, wszystkie typy zrobić mają wspólnego przodka, ale co mam na myśli to:Czy funkcja zwraca jeden z wielu typów, które nie mają wspólnego przodka?

W dynamicznie typowanych języków, jest to powszechna praktyka mieć „mieszane” typy powrotne. Typowym przypadkiem jest funkcja, która próbuje pobrać dane z bazy danych, a następnie zwraca obiekt (zainicjalizowany znalezionymi danymi) lub FALSE (w przypadku braku danych).

Trochę pseudokod wykazać tylko taką anty-wzorzec:

function getObjectFromDatabase(object_id) { 
    if(result = db_fetch_object("SELECT * FROM objects WHERE id = %d", object_id) { 
    return result 
    } else { 
    return FALSE 
    } 
} 

Jeśli dane można znaleźć na moim ID obiektu, otrzymuję rekordu DB powrotem jako przedmiot. Jeśli nie, otrzymam wartość boolowską. Wtedy oczywiście to ja, klient, obsługuję wiele możliwych typów zwrotu.

Czy jest to jedyny sposób, aby to zrobić w Scali, aby znaleźć wspólnego przodka dla wszystkich możliwych typów zwrotu i zadeklarować to jako typ zwrotu w podpisie?

// Like so: 
def getObjectFromDatabase(objectId: Int): Any = { 
    val result = dbFetchObject("SELECT * FROM objects WHERE id = %d", object_id) 
    if(result) { 
    return result 
    } else { 
    return false 
    } 
} 

Czy możliwe jest przypisywanie wielu możliwych typów zwrotu?

(Zauważ, że nie mam nadzieja możliwe jest, aby to zrobić, jak bym go wolą być egzekwowane, że typy powrotne funkcja są tak jednoznaczne, jak to możliwe. Wydaje się jako pomoc do mnie, aby dowiedzieć się, że . język zabrania niejednoznaczne rodzajów powrotne, które jest bardziej powodem pytam)

Odpowiedz

16

Co szukasz jest nazywany rekord z wariantami, wariant, rekord wariant, dyskryminowane unia, suma rozłączna lub typ suma.

połączeniu z rodzajów produktów, stają algebraiczne datatypes.

Scala nie ma bezpośredniego wsparcia dla algebraicznych typów danych, ale nie musi, ponieważ można je łatwo modelować przez dziedziczenie. (. Scala robi mają sealed modyfikator wspierania zamknięte ADTS)

W przykładzie, jeśli wiesz, że typ zwracany jest albo SomeType lub SomeOtherType można modelować to tak:

sealed trait ReturnType 

final case class SomeType extends ReturnType 
final case class SomeOtherType extends ReturnType 

def meth: ReturnType 

Jeśli nie wiesz, co typy zwracane są tylko, że istnieją dwa z nich, a następnie można je modelować podobnie:

sealed trait ReturnType[A, B] 

final case class Type1[A, B](a: A) extends ReturnType[A, B] 
final case class Type2[A, B](b: B) extends ReturnType[A, B] 

def meth: ReturnType[A, B] 

Jest to właściwie znany typ danych o nazwie Either (ponieważ jest to albo A lub B) i jest obecny w standardowej bibliotece Scala jako scala.util.Either.

Ale w twoim konkretnym przypadku istnieje bardziej szczegółowy typ, o nazwie Maybe lub Option, który zawiera pojęcie, że wartość może istnieć, a może nie. Wygląda to mniej więcej tak:

sealed trait Maybe[T] 

case object None extends Maybe[Nothing] 
final case class Just[T](value: T) extends Maybe[T] 

def meth: Maybe[T] 

Ponownie, jest to już przewidziane przez Scala jako scala.Option.

Zaletą Either nad Option jest to, że pozwala również zwrócić informacji w przypadku awarii, a nie tylko oznacza, że ​​nie ma wartości można również powiedzieć dlaczego nie ma wartości. (Zgodnie z konwencją lewa strona Either jest błędem, prawa strona jest wartością "użyteczną".)

Zaletą Option jest to, że jest to monada. (Uwaga: możesz ustawić monadę na Either, przesuwając ją w lewo lub w prawo.)

2

Jeśli będziesz wiedzieć w czasie wykonywania jakiego typu jesteś zapytaniem w każdej rozmowie, podpis może być tak:

def getObjectFromDatabase[T](object_id: Int): T = { 

Lub, aby zasymulować swoją logikę if/else, polecam użycie opcji tutaj:

def getObjectFromDatabase[T](object_id: Int): Option[T] = { 
    ... 
    if(result) Some(result) 
    else None 
} 

Przykład użycia:

val result = getObjectFromDatabase[String](123123).getOrElse(whatever_you_need) 
20

Tak, użyj Either:

def getObjectFromDatabase(objectId: Int): Either[Boolean, DbResult] = { 
    val result = dbFetchObject("SELECT * FROM objects WHERE id = %d", object_id) 
    if (result) Right(result) else Left(false) 

} 

getObjectFromDatabase(id) match { 
    case Right(result) => // do something with result 
    case Left(bool) => // do something with bool 
} 

Lub, jeśli sprawa brak wyników nie wymaga określonej wartości, użyj Option:

def getObjectFromDatabase(objectId: Int): Option[DbResult] = { 
    val result = dbFetchObject("SELECT * FROM objects WHERE id = %d", object_id) 
    if (result) Some(result) else None 
} 

getObjectFromDatabase(id) match { 
    case Some(result) => // do something with result 
    case None => // do something about no results 
} 

Zobacz Tony Morr to "Option Cheat Sheet", gdzie znajduje się lista najpopularniejszych metod, które można wywołać pod numerem Option i jak tłumaczą się na dopasowywanie wzorców.

Dwie inne alternatywy to Validation ze scalaza i Try, nowe w Scali 2.10.

Dla Validation istnieje kilka naprawdę dobrych odpowiedzi na StackOverflow, na przykład: Method parameters validation in Scala, with for comprehension and monads.

Dla Try zobacz ten wpis na blogu: The Neophyte's Guide to Scala Part 6: Error Handling With Try. Ten sam autor ma dobre posty na Option i Either.

+0

'Albo' jest prawidłowym wyborem tylko wtedy, gdy zamierzony typ powrotu może logicznie być tylko jednym z 2 typów. Nie jest to dobre logiczne rozwiązanie, jeśli koncepcyjnie możesz później dodać inny typ. tj. nie ma czegoś takiego jak "Albo [Int, String, Double]" – Adrian

Powiązane problemy