2014-08-30 13 views
10

BiorącDlaczego nie mogę wypróbować FlatMap?

val strings = Set("Hi", "there", "friend") 
def numberOfCharsDiv2(s: String) = scala.util.Try { 
    if (s.length % 2 == 0) s.length/2 else throw new RuntimeException("grr") 
} 

Dlaczego nie mogę flatMap dala Try wynikającą z wywołania metody? tj

strings.flatMap(numberOfCharsDiv2) 
<console>:10: error: type mismatch; 
found : scala.util.Try[Int] 
required: scala.collection.GenTraversableOnce[?] 
       strings.flatMap(numberOfCharsDiv2) 

lub

for { 
    s <- strings 
    n <- numberOfCharsDiv2(s) 
} yield n 
<console>:12: error: type mismatch; 
found : scala.util.Try[Int] 
required: scala.collection.GenTraversableOnce[?] 
      n <- numberOfCharsDiv2(s) 

Jednak jeśli mogę użyć opcji zamiast Spróbuj to nie ma problemu.

def numberOfCharsDiv2(s: String) = if (s.length % 2 == 0) 
    Some(s.length/2) else None 
strings.flatMap(numberOfCharsDiv2) # => Set(1, 3) 

Jakie jest uzasadnienie nie pozwala na flatMap spróbować?

+2

Z 'Option' działa tylko dlatego, że istnieje niejawna konwersja' Opcja [A] => Iterable [A] '. Wolałbym zapytać, jakie jest uzasadnienie tej konwersji. – ghik

+1

Abyśmy mogli wyobrazić sobie "Opcję" jako listę ograniczoną do rozmiaru max 1. Super przydatny, nie? – Synesso

+1

Używam niejawnej konwersji 'Option [A] => Iterable [A]' przez cały czas. Uważam, że warto pomyśleć o opcji jako pojemniku od 0 do 1. – acjay

Odpowiedz

10

Spójrzmy na podpisaniu flatMap.

def flatMap[B](f: (A) => GenTraversableOnce[B]): Set[B] 

Twój numberOfCharsDiv2 jest postrzegana jako String => Try[Int]. Try nie jest podklasą GenTraversableOnce i dlatego pojawia się błąd. Nie potrzebujesz dokładnie funkcji, która daje Set tylko dlatego, że używasz flatMap na Set. Funkcja zasadniczo musi zwrócić każdy rodzaj kolekcji.

Dlaczego więc działa z Option? Option nie jest również podklasą GenTraversableOnce, ale istnieje niejawna konwersja wewnątrz obiektu towarzyszącego Option, która przekształca go w obiekt List.

implicit def option2Iterable[A](xo: Option[A]): Iterable[A] = xo.toList 

Pozostaje jedno pytanie. Dlaczego nie ma również niejawnej konwersji dla Try? Ponieważ prawdopodobnie nie dostaniesz tego, czego chcesz.

flatMap może być postrzegany jako map, a następnie flatten.

Wyobraź sobie, że masz List[Option[Int]], takie jak List(Some(1), None, Some(2)). Wtedy flatten da Ci List(1,2) typu List[Int].

Teraz spójrz na przykład z Try. List(Success(1), Failure(exception), Success(2)) typu List[Try[Int]].

Jak spłaszczy się teraz praca z awarią?

  • Czy powinien zniknąć jak None? Dlaczego więc nie pracować bezpośrednio z Option?
  • Czy powinien zostać uwzględniony w wyniku? Wtedy byłby to List(1, exception, 2). Problem polega na tym, że typem jest List[Any], ponieważ musisz znaleźć wspólną super klasę dla Int i Throwable. Tracisz typ.

Powody, dla których nie ma konwersji niejawnej, powinny być uzasadnione. Oczywiście, możesz sam zdefiniować siebie, jeśli zaakceptujesz powyższe konsekwencje.

+0

Dobra odpowiedź, szczególnie część na typy, "Brak" nie ma żadnej wartości i można je łatwo i bezpiecznie usunąć, "Awarie" zamiast tego trzymają wartość, która ma znaczenie i dlatego nie może (i nie powinna) zostać usunięta, ale Przechowywanie ich oznaczałoby, że jeśli tam, gdzie konwersja zakończyłaby się listą informacji i bezpieczeństwa typu "Any", to jest to dość mocny argument. –

+0

Rozumiem twoją odpowiedź, ale myślę, że to nie jest właściwy projekt w imieniu std lib. Typem danych jest 'Try [A]', więc zależy mi tylko na 'A's. Powinienem móc spłaszczyć "Try", odrzucając wartości 'Failure' - tak jak w przypadku semantyki' Option'. – Synesso

+2

Tak, 'Try' ma tylko jeden parametr ogólny. Ale tylko dlatego, że typ dla 'Failure' jest już ustawiony na' Throwable' i nie wymaga dalszych specyfikacji. Używasz 'Try', gdy zależy Ci również na wyjątkach. Jeśli nie: użyj opcji "Option" od początku lub użyj metody 'toOption' w swoim' Try'. Dla twojego przypadku: 'strings.flatMap (numberOfCharsDiv2 (_). ToOption)' – Kigyo

5

Problem polega na tym, że w twoim przykładzie nie jesteś spłaszczony przez Wypróbuj. Flatmap, który robisz, minął Set.

Przebicie mapy przez zestaw zajmuje ustawienie [A], a funkcję od A do ustawienia [B]. Jak Kígyó wskazuje w jego komentarz poniżej to nie jest rzeczywisty podpis rodzaj flatmap na planie w Scala, ale ogólna postać płaskiej mapie jest:

M[A] => (A => M[B]) => M[B]

Oznacza to, że zajmuje trochę wyższe kinded type, wraz z funkcją, która działa na elementach tego typu w typie wyższego typu, i daje ten sam typ wyższego typu z odwzorowanymi elementami.

W twoim przypadku oznacza to, że dla każdego elementu zestawu, Flatmap oczekuje wywołania funkcji, która pobiera łańcuch, i zwraca zestaw typu B, który może być ciągiem (lub czymkolwiek innym).

Twoja funkcja

numberOfCharsDiv2(s: String) 

poprawnie pobiera ciąg, ale nieprawidłowo zwraca próbować, a następnie inny zestaw jako wymagającego flatmap.

Twój kod zadziała, jeśli użyjesz "mapy", ponieważ pozwala to na zajęcie pewnej struktury - w tym przypadku Ustaw i uruchom funkcję nad każdym elementem przekształcając ją z A na B bez typu powrotu funkcji zgodnego z struktura otaczająca czyli zwracanie Set

strings.map(numberOfCharsDiv2)

res2: scala.collection.immutable.Set[scala.util.Try[Int]] = Set(Success(1), Failure(java.lang.RuntimeException: grr), Success(3))

+0

'flatMap' over' Set [A] 'potrzebuje funkcji z' A => GenTraversableOnce [B] '. Nie musi to być "Set [B]". – Kigyo

+0

Cześć Kigyo. Tak, zdaję sobie sprawę, że tak jest w tym konkretnym przypadku, starałem się mówić więcej o ogólnym przypadku płaskiej mapy lub powiązania w haskell, którego struktura jest następująca: (M [A]) (A => M [B]): M [B]. – bobbyr

+0

Niestety, to odpowiedź na źle zinterpretowane pytanie. – Synesso

Powiązane problemy