2012-03-20 14 views
13

Quando costruisco una lista utilizzando foldLeft Ho spesso infastidito dover digitare in modo esplicito il parametro iniettato e avrei voluto semplicemente usare `Nil' invece - Ecco un esempio artificioso:Perché non passa Nil a piegarsi?

scala> List(1,2,3).foldLeft(List[Int]())((x,y) => y :: x) 
res17: List[Int] = List(3, 2, 1) 

scala> List(1,2,3).foldLeft(Nil)((x, y) => y :: x) 
<console>:10: error: type mismatch; 
found : List[Int] 
required: scala.collection.immutable.Nil.type 
       List(1,2,3).foldLeft(Nil)((x,y) => y :: x) 

Questo non è poi così male con un List[Int] ma non appena inizi a utilizzare elenchi delle tue classi, che quasi sicuramente avranno nomi più lunghi, o persino elenchi di tuple o altri contenitori, quindi ci sono più nomi di classe che devi specificare, diventa orrendo:

list.foldLeft(List.empty[(SomethingClass, SomethingElseClass)]) { (x,y) => y :: x } 

Sto supponendo che il re ma non funziona, mentre con qualcosa come 5 :: Nil il compilatore può dedurre il tipo della lista vuota per essere List[Int], ma quando Nil viene passato come parametro a foldLeft non ha abbastanza informazioni per farlo, e da il tempo in cui arriva a essere usato il suo tipo è impostato. Ma - è proprio vero che non poteva? Non potrebbe inferire il tipo dal tipo restituito della funzione passata come secondo argomento?

E se no, c'è qualche idioma più ordinario di cui non so nulla?

+1

È deplorevole che Scala non possa inferire il tipo per questo tipo di situazioni. Speriamo che questo sia modificato in futuro. –

+0

Non dovrebbe essere troppo un problema nella pratica. Se hai a che fare con alcune classi specifiche, fai un alias di tipo in alto 'type S = (SomethingClass, SomethingElseClass)' o 'val nil = List.empty [(SomethingClass, SomethingElseClass)]'. Se stai scrivendo un metodo generico, i tuoi tipi avranno già alias brevi come 'T' e' U'. –

risposta

14

Il motore di inferenza di tipo scalas funziona da sinistra a destra, pertanto Scalac non può inferire il tipo corretto per il primo elenco di parametri di foldLeft. Devi dare al compilatore un suggerimento su che tipo usare. Invece di utilizzare List[TYPE]() è possibile utilizzare List.empty[TYPE]:

(List(1,2,3) foldLeft List.empty[Int]) { (x,y) => y :: x } 
(List.empty[Int] /: List(1, 2, 3)) { (x,y) => y :: x } 
+0

Quindi non c'è modo più breve intorno a questo? Quando è ad esempio un elenco di tuple, inizia a sembrare ridicolo: 'list.foldLeft (List.empty [(SomethingClass, SomethingElseClass)]) {(x, y) => y :: x}'. Occupa un'intera linea e oscura il significato di ciò che stai effettivamente cercando di fare! – Russell

+3

@Russell: No, non esiste un modo più breve. L'unica cosa che puoi fare è definire un alias di tipo che è utile quando hai più occorrenze del tipo "lungo". – sschaef

+0

Grazie sia per le vostre risposte, dato che non posso accettarle entrambe ne accetterò una e revocherò l'altra. – Russell

12

inferenza di tipo di Scala lavora un blocco parametro alla volta. Nil senza nessun altro contesto non ha alcun tipo particolare, quindi viene scelto il tipo più restrittivo possibile (Nothing).

Inoltre, non è possibile inferire il tipo di restituzione della funzione poiché il tipo di reso dipende dal tipo di Nil. Uscire da quel tipo di circolarità è complicato in generale (se non si specifica cosa intendi).

Ci sono alcuni trucchi che è possibile applicare, tuttavia, per rendere le cose meno ingombranti.

Innanzitutto, la firma del tipo di fold ha solo il tipo di raccolta, quindi se è possibile utilizzare quel tipo di piegatura, è possibile aggirare il problema. Ad esempio, è possibile scrivere il proprio reverse-appiattire:

List(List(1),List(2),List(3)).fold(Nil)((x,y) => y ::: x) 

In secondo luogo, se si sta creando una nuova collezione dello stesso tipo, è spesso più facile da usare la collezione esistente di generare una vuota piuttosto che cercare per collegare i tipi per una raccolta vuota. E 'particolarmente facile se avete tubo definito da qualche parte:

class PipeAnything[A](a: A) { def |>[B](f: A => B) = f(a) } 
implicit def anything_can_be_piped[A](a: A) = new PipeAnything(a) 

List(1,2,3) |> { x => x.foldLeft(x.take(0))((y,z) => z :: y) } 

Infine, non dimenticate che è possibile definire i propri metodi abbastanza facilmente che può fare qualcosa di coerente con i tipi di, anche se si deve usare un po' di inganno per farlo funzionare:

def foldMe[A,B](example: A, list: List[B])(f: (List[A],B) => List[A]) = { 
    (List(example).take(0) /: list)(f) 
} 

scala> foldMe(("",0), List("fish","wish","dish"))((x,y) => (y.take(1), y.length) :: x) 
res40: List[(java.lang.String, Int)] = List((d,4), (w,4), (f,4)) 

Qui, si noti che l'esempio non è uno zero, ma è usato solo come un esemplare della cosa con il tipo che si desidera.

+0

Grazie sia per le vostre risposte, dato che non posso accettarle entrambe ne accetterò una e revocherò l'altra. – Russell

Problemi correlati