Scala per uno strano comportamento

Voglio scorrere un elenco di valori usando un bellissimo one-liner in Scala.

Ad esempio, questo funziona bene:

scala> val x = List(1,2,3,4) x: List[Int] = List(1, 2, 3, 4) scala> x foreach println 1 2 3 4 

Ma se uso il segnaposto _ , mi dà un errore:

 scala> x foreach println(_ + 1) :6: error: missing parameter type for expanded function ((x$1) =>x$1.$plus(1)) x foreach println(_ + 1) ^ 

Perché? Il compilatore non può inferire qui il tipo?

Questo:

 x foreach println(_ + 1) 

è equivalente a questo:

 x.foreach(println(x$1 => x$1 + 1)) 

Non c’è alcuna indicazione su quale potrebbe essere il tipo di x$1 , e, ad essere onesti, non ha senso stampare una funzione.

Ovviamente (per me) intendevo stampare x$0 + 1 , dove x$0 sarebbe il parametro passato foreach , invece. Ma consideriamo questo … foreach accetta, come parametro, una Function1[T, Unit] , dove T è il parametro type della lista. Quello che stai passando a foreach è invece println(_ + 1) , che è un’espressione che restituisce Unit .

Se hai scritto, invece x foreach println , x foreach println una cosa completamente diversa. Passerai la funzione (*) println , che accetta Any e restituisce Unit , adattando, quindi, i requisiti di foreach .

Ciò viene leggermente confuso a causa delle regole di espansione di _ . Si espande al delimitatore di espressione più interno (parentesi o parentesi graffe), tranne se sono al posto di un parametro, nel qual caso significa una cosa diversa: applicazione di funzione parziale.

Per spiegarlo meglio, guarda questi esempi:

 def f(a: Int, b: Int, c: Int) = a + b + c val g: Int => Int = f(_, 2, 3) // Partial function application g(1) 

Qui, applichiamo il secondo e il terzo argomento a f , e restituiamo una funzione che richiede solo l’argomento rimanente. Si noti che ha funzionato solo perché ho indicato il tipo di g , altrimenti avrei dovuto indicare il tipo di argomento che non stavo applicando. Continuiamo:

 val h: Int => Int = _ + 1 // Anonymous function, expands to (x$1: Int => x$1 + 1) val i: Int => Int = (_ + 1) // Same thing, because the parenthesis are dropped here val j: Int => Int = 1 + (_ + 1) // doesn't work, because it expands to 1 + (x$1 => x$1 + 1), so it misses the type of `x$1` val k: Int => Int = 1 + ((_: Int) + 1) // doesn't work, because it expands to 1 + (x$1: Int => x$1 + 1), so you are adding a function to an `Int`, but this operation doesn't exist 

Parliamo k più in dettaglio, perché questo è un punto molto importante. Ricorda che g è una funzione Int => Int , giusto? Quindi, se dovessi digitare 1 + g , avrebbe senso? Questo è ciò che è stato fatto in k .

Ciò che confonde le persone è che ciò che volevano veramente era:

 val j: Int => Int = x$1 => 1 + (x$1 + 1) 

In altre parole, vogliono che x$1 sostituisca _ per saltare all’esterno della parentesi e nel posto giusto. Il problema qui è che, mentre può sembrare ovvio per loro quale sia il posto giusto, non è ovvio per il compilatore. Considera questo esempio, ad esempio:

 def findKeywords(keywords: List[String], sentence: List[String]) = sentence.filter(keywords contains _.map(_.toLowerCase)) 

Ora, se dovessimo estendere questo al di fuori della parentesi, avremmo questo:

 def findKeywords(keywords: List[String], sentence: List[String]) = (x$1, x$2) => sentence.filter(keywords contains x$1.map(x$2.toLowerCase)) 

Che non è sicuramente quello che vogliamo. Infatti, se il _ non è stato limitato dal delimitatore di espressione più interno, non è mai ansible usare _ con map nidificata, flatMap , filter e foreach .

Ora, tornando alla confusione tra la funzione anonima e l’applicazione parziale, guarda qui:

 List(1,2,3,4) foreach println(_) // doesn't work List(1,2,3,4) foreach (println(_)) // works List(1,2,3,4) foreach (println(_ + 1)) // doesn't work 

La prima riga non funziona a causa di come funziona la notazione operazione. Scala vede solo che println restituisce Unit , che non è quello che foreach aspetta.

La seconda linea funziona perché la parentesi consente a Scala di valutare println(_) nel suo insieme. È un’applicazione di funzione parziale, quindi restituisce Any => Unit , che è accettabile.

La terza riga non funziona perché _ + 1 è la funzione anonima, che stai passando come parametro a println . Non stai facendo parte di una funzione anonima, che è ciò che volevi.

Infine, ciò che pochi si aspettano:

 List(1,2,3,4) foreach (Console println _ + 1) 

Questo funziona. Perché lo fa è lasciato come un esercizio al lettore. 🙂

(*) In realtà, println è un metodo. Quando scrivi x foreach println , non stai passando un metodo, perché i metodi non possono essere passati. Invece, Scala crea una chiusura e la passa. Si espande in questo modo:

 x.foreach(new Function1[Any,Unit] { def apply(x$1: Any): Unit = Console.println(x$1) }) 

Il trattino basso è un po ‘complicato. Secondo le specifiche, la frase:

 _ + 1 

è equivalente a

 x => x + 1 

Provando

 x foreach println (y => y + 1) 

rendimenti:

 :6: error: missing parameter type x foreach println (y => y + 1) 

Se aggiungi alcuni tipi in:

 x foreach( println((y:Int) => y + 1)) :6: error: type mismatch; found : Unit required: (Int) => Unit x foreach( println((y:Int) => y + 1)) 

Il problema è che stai trasmettendo una funzione anonima a println e che non è in grado di gestirlo. Quello che vuoi veramente fare (se stai provando a stampare il successore di ciascun elemento nell’elenco) è:

 x map (_+1) foreach println 
 scala> for(x <- List(1,2,3,4)) println(x + 1) 2 3 4 5 

C’è una strana limitazione in Scala per la profondità di annidamento delle espressioni con underscore. È ben visto nel seguente esempio:

  scala> List(1) map(1+_) res3: List[Int] = List(2) scala> Some(1) map (1+(1+_)) :5: error: missing parameter type for expanded function ((x$1) => 1.+(x$1)) Some(1) map (1+(1+_)) ^ 

Sembra un bug per me.

 Welcome to Scala version 2.8.0.Beta1-prerelease (Java HotSpot(TM) Client VM, Java 1.6.0_17). Type in expressions to have them evaluated. Type :help for more information. scala> val l1 = List(1, 2, 3) l1: List[Int] = List(1, 2, 3) scala> scala> l1.foreach(println(_)) 1 2 3