Quelles sont toutes les utilisations d'un trait de soulignement dans Scala?

540

J'ai jeté un coup d'œil à la liste des sondages effectués sur scala-lang.org et j'ai remarqué une curieuse question: " Pouvez-vous nommer toutes les utilisations de" _ "? ". Peut tu? Si oui, veuillez le faire ici. Des exemples explicatifs sont appréciés.

Ivan
la source
15
J'ai lu cet ensemble de diapositives décent il n'y a pas longtemps: Scala Dreaded Underscore
Dan Burton
5
Voir aussi la présentation de The Dreaded _ sur Slideshare
Mifeet

Réponses:

576

Ceux auxquels je peux penser sont

Types existentiels

def foo(l: List[Option[_]]) = ...

Paramètres de type plus élevés

case class A[K[_],T](a: K[T])

Variables ignorées

val _ = 5

Paramètres ignorés

List(1, 2, 3) foreach { _ => println("Hi") }

Noms ignorés des types personnels

trait MySeq { _: Seq[_] => }

Motifs génériques

Some(5) match { case Some(_) => println("Yes") }

Motifs génériques dans les interpolations

"abc" match { case s"a$_c" => }

Caractère générique de séquence dans les modèles

C(1, 2, 3) match { case C(vs @ _*) => vs.foreach(f(_)) }

Importations génériques

import java.util._

Masquer les importations

import java.util.{ArrayList => _, _}

Joindre des lettres aux opérateurs

def bang_!(x: Int) = 5

Opérateurs d'affectation

def foo_=(x: Int) { ... }

Syntaxe de l'espace réservé

List(1, 2, 3) map (_ + 2)

Valeurs de méthode

List(1, 2, 3) foreach println _

Conversion de paramètres d'appel par nom en fonctions

def toFunction(callByName: => Int): () => Int = callByName _

Initialiseur par défaut

var x: String = _   // unloved syntax may be eliminated

Il y en a peut-être d'autres que j'ai oubliés!


Exemple montrant pourquoi foo(_)et foo _sont différents:

Cet exemple vient de 0__ :

trait PlaceholderExample {
  def process[A](f: A => Unit)

  val set: Set[_ => Unit]

  set.foreach(process _) // Error 
  set.foreach(process(_)) // No Error
}

Dans le premier cas, process _représente une méthode; Scala prend la méthode polymorphe et tente de la rendre monomorphe en remplissant le paramètre type, mais se rend compte qu'il n'y a pas de type qui puisse être rempli car Acela donnera le type (_ => Unit) => ?(Existential _n'est pas un type).

Dans le deuxième cas, process(_)est un lambda; lors de l'écriture d'un lambda sans type d'argument explicite, Scala déduit le type de l'argument qui l' foreachattend et _ => Unit est un type (alors que ce _n'est pas le cas), il peut donc être substitué et déduit.

C'est peut-être le piège le plus délicat de Scala que j'aie jamais rencontré.

Notez que cet exemple se compile en 2.13. Ignorez-le comme s'il avait été attribué au trait de soulignement.

Owen
la source
4
Je pense qu'il y en a deux ou trois qui correspondent tous à l'utilisation de soulignement dans la correspondance de modèles, mais +1 pour joindre des lettres à la ponctuation! :-)
Daniel C. Sobral
22
val x: Any = _
Giovanni Botta
2
@Owen Je ne pense pas que println _ soit une fonction partiellement appliquée. C'est un autre exemple de syntaxe d'espace réservé non? Signification map (_ + 2) se développe à quelque chose de similaire à map (x => x + 2) tout comme pritnln (_) se développe à quelque chose de similaire à map (x => println (x))
Andrew Cassidy
7
@AndrewCassidy En fait println _et println(_)sont différents. Vous pouvez voir cela par exemple en ce qu'ils gèrent les types existentiels et polymorphes légèrement différemment. Viendra avec un exemple dans un peu.
Owen
3
@AndrewCassidy OK J'ai ajouté un exemple.
Owen
179

De (mon entrée) dans la FAQ , que je ne garantis certainement pas d'être complète (j'ai ajouté deux entrées il y a seulement deux jours):

import scala._    // Wild card -- all of Scala is imported
import scala.{ Predef => _, _ } // Exception, everything except Predef
def f[M[_]]       // Higher kinded type parameter
def f(m: M[_])    // Existential type
_ + _             // Anonymous function placeholder parameter
m _               // Eta expansion of method into method value
m(_)              // Partial function application
_ => 5            // Discarded parameter
case _ =>         // Wild card pattern -- matches anything
val (a, _) = (1, 2) // same thing
for (_ <- 1 to 10)  // same thing
f(xs: _*)         // Sequence xs is passed as multiple parameters to f(ys: T*)
case Seq(xs @ _*) // Identifier xs is bound to the whole matched sequence
var i: Int = _    // Initialization to the default value
def abc_<>!       // An underscore must separate alphanumerics from symbols on identifiers
t._2              // Part of a method name, such as tuple getters
1_000_000         // Numeric literal separator (Scala 2.13+)

Cela fait également partie de cette question .

Daniel C. Sobral
la source
2
Peut-être que vous pouvez ajouter var i: Int = _ou le cas spécial de la correspondance de motifs val (a, _) = (1, 2)ou le cas spécial de val jetéfor (_ <- 1 to 10) doIt()
huynhjl
1
Et un def f: T; def f_=(t: T)combo pour créer un membre f mutable.
huynhjl
La correspondance de motifs est déjà couverte, et _sur les noms de méthode, c'est de la triche. Mais bon, ok. J'espère juste que quelqu'un d'autre mettra à jour la FAQ ... :-)
Daniel C. Sobral
1
Peut-être que vous manquez celui-ci. vertx.newHttpServer.websocketHandler (_. writeXml (html))
angelokh
@angelokh C'est le paramètre d'espace réservé de fonction anonyme, cinquième en bas de la liste.
Daniel C.Sobral
84

Une excellente explication des utilisations du trait de soulignement est Scala _ [underscore] magic .

Exemples:

 def matchTest(x: Int): String = x match {
     case 1 => "one"
     case 2 => "two"
     case _ => "anything other than one and two"
 }

 expr match {
     case List(1,_,_) => " a list with three element and the first element is 1"
     case List(_*)  => " a list with zero or more elements "
     case Map[_,_] => " matches a map with any key type and any value type "
     case _ =>
 }

 List(1,2,3,4,5).foreach(print(_))
 // Doing the same without underscore: 
 List(1,2,3,4,5).foreach( a => print(a))

Dans Scala, _agit de la même manière *qu'en Java lors de l'importation de packages.

// Imports all the classes in the package matching
import scala.util.matching._

// Imports all the members of the object Fun (static import in Java).
import com.test.Fun._

// Imports all the members of the object Fun but renames Foo to Bar
import com.test.Fun.{ Foo => Bar , _ }

// Imports all the members except Foo. To exclude a member rename it to _
import com.test.Fun.{ Foo => _ , _ }

Dans Scala, un getter et un setter seront définis implicitement pour toutes les variables non privées d'un objet. Le nom du getter est identique au nom de la variable et _=est ajouté pour le nom du setter.

class Test {
    private var a = 0
    def age = a
    def age_=(n:Int) = {
            require(n>0)
            a = n
    }
}

Usage:

val t = new Test
t.age = 5
println(t.age)

Si vous essayez d'affecter une fonction à une nouvelle variable, la fonction sera invoquée et le résultat sera affecté à la variable. Cette confusion se produit en raison des accolades facultatives pour l'appel de méthode. Nous devons utiliser _ après le nom de la fonction pour l'assigner à une autre variable.

class Test {
    def fun = {
        // Some code
    }
    val funLike = fun _
}
JAiro
la source
2
C'est une bonne explication, mais elle ne les contient même pas toutes. Il manque des paramètres / variables ignorés, des lettres de jonction et de la ponctuation, des types existentiels, des types plus élevés
Owen
dans votre, List(1,2,3,4,5).foreach(print(_))c'est beaucoup plus lisible à faire List(1,2,3,4,5).foreach(print), vous n'avez même pas vraiment besoin du soulignement du tout, mais je suppose que c'est juste une question de style
Electric Coffee
1
que diriez-vous que "_" fonctionne comme espace réservé dans les collections avec la fonction .map, .flatten, .toList ...... Parfois, cela me fait mal comprendre. :(
m0z4rt
34

Il y a un usage que je peux voir que tout le monde ici semble avoir oublié de lister ...

Plutôt que de faire cela:

List("foo", "bar", "baz").map(n => n.toUpperCase())

Vous pouvez simplement faire ceci:

List("foo", "bar", "baz").map(_.toUpperCase())
Café électrique
la source
alors _ agit ici comme un espace de noms de toutes les fonctions disponibles?
Crt
2
@Crt non, il agit comme un raccourci pourn => n
Electric Coffee
2
N'est-ce pas la syntaxe d'espace réservé mentionnée dans les deux premières réponses?
joelb
13

Voici quelques autres exemples où _est utilisé:

val nums = List(1,2,3,4,5,6,7,8,9,10)

nums filter (_ % 2 == 0)

nums reduce (_ + _)

nums.exists(_ > 5)

nums.takeWhile(_ < 8)

Dans tous les exemples ci-dessus, un trait de soulignement représente un élément de la liste (pour réduire le premier trait de soulignement représente l'accumulateur)

swaraj patil
la source
11

Outre les usages mentionnés par JAiro, j'aime bien celui-ci:

def getConnectionProps = {
    ( Config.getHost, Config.getPort, Config.getSommElse, Config.getSommElsePartTwo )
}

Si quelqu'un a besoin de toutes les propriétés de connexion, il peut faire:

val ( host, port, sommEsle, someElsePartTwo ) = getConnectionProps

Si vous n'avez besoin que d'un hôte et d'un port, vous pouvez faire:

val ( host, port, _, _ ) = getConnectionProps
tolitius
la source
0

Il existe un exemple spécifique d'utilisation de "_":

  type StringMatcher = String => (String => Boolean)

  def starts: StringMatcher = (prefix:String) => _ startsWith prefix

peut être égal à:

  def starts: StringMatcher = (prefix:String) => (s)=>s startsWith prefix

L'application de «_» dans certains scénarios se convertira automatiquement en «(x $ n) => x $ n»

Ke.Steve
la source
sentir l'exemple de tout le monde est un élément de l'itération, je pense que cela ressemble plus à un sucre de syntaxe de bas niveau, a déclaré la conversion concise lambda
Ke.Steve