2014-08-30 20 views
10

DatoPerché non riesco a flatMap a Try?

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

Perché non posso flatMap via la Prova risultante dalla chiamata di metodo? vale a dire

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

o

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) 

Tuttavia se uso opzione invece di Prova non c'è nessun problema.

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

Qual è la logica alla base del non consentire flatMap su Prova?

+2

Con 'Option' funziona solo perché esiste una conversione implicita' Opzione [A] => Iterable [A] '. Preferirei chiedere qual è la logica alla base di questa conversione. – ghik

+1

In modo che possiamo concepire un 'Opzione' come una lista limitata a 1 dimensione. Super pratico, no? – Synesso

+1

Uso la conversione implicita 'Opzione [A] => Iterable [A]' sempre. Penso che sia abbastanza utile pensare a un'opzione come un contenitore 0-a-1. – acjay

risposta

10

guardiamo la firma di flatMap.

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

tuo numberOfCharsDiv2 è visto come String => Try[Int]. Try non è una sottoclasse di GenTraversableOnce ed è per questo che si ottiene l'errore. Non è strettamente necessario una funzione che fornisce un valore Set solo perché si utilizza flatMap su un Set. La funzione deve fondamentalmente restituire qualsiasi tipo di raccolta.

Quindi perché funziona con Option? Option non è una sottoclasse di GenTraversableOnce, ma esiste una conversione implicita all'interno dell'oggetto complementare Option, che lo trasforma in List.

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

Quindi rimane una domanda. Perché non avere una conversione implicita per Try? Perché probabilmente non otterrai quello che vuoi.

flatMap può essere visto come map seguito da un flatten.

Immagina di avere un List[Option[Int]] come List(Some(1), None, Some(2)). Quindi flatten ti darà List(1,2) di tipo List[Int].

Ora guarda un esempio con Try. List(Success(1), Failure(exception), Success(2)) di tipo List[Try[Int]].

Come si appiattirà ora l'errore?

  • Dovrebbe scomparire come None? Allora perché non lavorare direttamente con Option?
  • Dovrebbe essere incluso nel risultato? Quindi sarebbe List(1, exception, 2). Il problema qui è che il tipo è List[Any], perché devi trovare una super classe comune per Int e Throwable. Si perde il tipo.

Queste dovrebbero essere le ragioni per cui non esiste una conversione implicita. Ovviamente sei libero di definirne uno tu stesso, se accetti le conseguenze di cui sopra.

+0

Bella risposta, specialmente la parte sui tipi, 'Nessuna' non ha valore e possono essere rimossi facilmente e in modo sicuro,' Errori 'invece contengono un valore che ha un significato e quindi non può (e non dovrebbe) essere rimosso, ma mantenerli significherebbe che se là dove una conversione si finirebbe con la lista di "Any" perdere informazioni di tipo e sicurezza, immagino che sia un argomento abbastanza forte. –

+0

Capisco la tua risposta, ma penso che non sia il progetto giusto per conto della std lib. Il tipo di dati è 'Prova [A]', quindi mi interessa solo il 'A's. Dovrei essere in grado di appiattire il 'Try', scartando i valori di' Failure' - proprio come per la semantica 'Option'. – Synesso

+2

Sì, 'Prova' ha solo un parametro generico. Ma solo perché il tipo di 'Failure' è già impostato su' Throwable' e non richiede ulteriori specifiche. Usi 'Prova' quando ti interessano anche le eccezioni. In caso contrario: utilizzare 'Option' dall'inizio o utilizzare il metodo' toOption' su 'Try'. Per il tuo caso: 'strings.flatMap (numberOfCharsDiv2 (_). ToOption)' – Kigyo

5

Il problema è che nel tuo esempio, non stai pianeggiando su Prova. La flatmap che stai facendo è su Set.

Flatmap su Set prende un Set [A] e una funzione da A a Set [B]. Come Kígyó sottolinea nel suo commento qui sotto non è questa la firma tipo effettivo di flatmap su Set in Scala, ma la forma generale di carta piatta è:

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

Cioè, ci vuole un po più alto kinded digita, insieme a una funzione che opera su elementi del tipo in quel tipo di tipo più alto, e ti restituisce lo stesso tipo di tipo più alto con gli elementi mappati.

Nel tuo caso, questo significa che per ogni elemento del tuo Set, flatmap si aspetta una chiamata a una funzione che accetta una stringa e restituisce un Set di qualche tipo B che potrebbe essere String (o potrebbe essere qualsiasi altra cosa).

La funzione

numberOfCharsDiv2(s: String) 

prende correttamente una stringa, ma in modo non corretto restituisce una prova, piuttosto che un altro insieme come flatmap richiede.

Il tuo codice funzionerebbe se tu avessi usato 'mappa', dato che ti permette di prendere qualche struttura - in questo caso Imposta ed esegui una funzione su ogni elemento trasformandolo da A a B senza il tipo di ritorno della funzione conforme a la struttura racchiude cioè la restituzione di un 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' su' Set [A] 'richiede una funzione da' A => GenTraversableOnce [B] '. Non ha bisogno di essere un 'Set [B] '. – Kigyo

+0

Ciao Kigyo. Sì, mi rendo conto che è il caso in questo caso specifico, stavo cercando di parlare di più del caso generale di flatmap, o di legare in haskell, la cui struttura è: (M [A]) (A => M [B]): M [B]. – bobbyr

+0

Sfortunatamente questo risponde a una domanda male interpretata. – Synesso

Problemi correlati