Jakie są wszystkie zastosowania podkreślenia w Scali?

Odpowiedzi:

576

Te, o których mogę myśleć, to

Rodzaje egzystencjalne

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

Wyższe parametry typu

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

Ignorowane zmienne

val _ = 5

Ignorowane parametry

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

Ignorowane nazwy własnych typów

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

Wzory symboli wieloznacznych

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

Wzory symboli wieloznacznych w interpolacjach

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

Symbol wieloznaczny sekwencji we wzorach

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

Importowanie symboli wieloznacznych

import java.util._

Ukrywanie importu

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

Łączenie listów z operatorami

def bang_!(x: Int) = 5

Operatorzy przypisań

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

Składnia zastępcza

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

Wartości metod

List(1, 2, 3) foreach println _

Konwertowanie parametrów Call-by-Name na funkcje

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

Domyślny inicjator

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

Mogą istnieć inne, o których zapomniałem!


Przykład pokazujący, dlaczego foo(_)i foo _są różne:

Ten przykład pochodzi z 0__ :

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

  val set: Set[_ => Unit]

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

W pierwszym przypadku process _reprezentuje metodę; Scala przyjmuje metodę polimorficzną i próbuje uczynić ją monomorficzną, wypełniając parametr type, ale zdaje sobie sprawę, że nie ma typu, który można by wypełnić A, który da typ (_ => Unit) => ?(Existential _nie jest typem).

W drugim przypadku process(_)jest lambda; pisząc lambda bez jawnego typu argumentu, Scala wybiera typ z argumentu, który się foreachspodziewa, i _ => Unit jest typem (podczas gdy zwykły _nie jest), więc można go podstawić i wywnioskować.

To może być najtrudniejsza gotcha w Scali, jaką kiedykolwiek spotkałem.

Zauważ, że ten przykład kompiluje się w 2.13. Zignoruj ​​to, jakby zostało przypisane do podkreślenia.

piekarnik
źródło
4
Myślę, że są dwa lub trzy, które pasują do użycia podkreślnika w dopasowywaniu wzorów, ale +1 za łączenie liter z interpunkcją! :-)
Daniel C. Sobral
22
val x: Any = _
Giovanni Botta,
2
@Owen Nie sądzę, że println _ jest częściowo zastosowaną funkcją. To kolejny przykład składni symboli zastępczych, prawda? Znaczenie mapy (_ + 2) rozszerza się czymś podobnym do mapowania (x => x + 2) tak jak pritnln (_) rozszerza się czymś podobnym do mapowania (x => println (x))
Andrew Cassidy
7
@AndrewCassidy rzeczywistości println _i println(_)są różne. Widać to na przykład w tym, że traktują one typy egzystencjalne i polimorficzne nieco inaczej. Wkrótce podam przykład.
Owen,
3
@AndrewCassidy OK Dodałem przykład.
Owen
179

Z (mojego wpisu) w FAQ , które z pewnością nie gwarantuję, że są kompletne (dodałem dwa wpisy zaledwie dwa dni temu):

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+)

To także część tego pytania .

Daniel C. Sobral
źródło
2
Może być możliwe dodanie var i: Int = _lub specjalny przypadek dopasowania wzoru val (a, _) = (1, 2)lub specjalny przypadek odrzuconego valfor (_ <- 1 to 10) doIt()
huynhjl
1
I def f: T; def f_=(t: T)kombinacja do tworzenia modyfikowalnego elementu F.
huynhjl
Dopasowywanie wzorców jest już uwzględnione, a _nazwy metod oszukują. Ale cóż, ok. Mam tylko nadzieję, że ktoś inny zaktualizuje FAQ ... :-)
Daniel C. Sobral,
1
Może tęsknisz za tym. vertx.newHttpServer.websocketHandler (_. writeXml (html))
angelokh
@angelokh To parametr zastępczy funkcji anonimowej, piąty w dół listy.
Daniel C. Sobral
84

Doskonałym wyjaśnieniem zastosowania podkreślenia jest magia Scala _ [podkreślenie] .

Przykłady:

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

W Scali _działa podobnie jak *w Javie podczas importowania pakietów.

// 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 => _ , _ }

W Scali getter i setter będą domyślnie zdefiniowane dla wszystkich nieprywatnych zmiennych w obiekcie. Nazwa gettera jest taka sama jak nazwa zmiennej i _=jest dodawana do nazwy setera.

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

Stosowanie:

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

Jeśli spróbujesz przypisać funkcję do nowej zmiennej, funkcja zostanie wywołana, a wynik zostanie przypisany do zmiennej. To zamieszanie występuje z powodu opcjonalnych nawiasów klamrowych do wywołania metody. Powinniśmy użyć _ po nazwie funkcji, aby przypisać ją do innej zmiennej.

class Test {
    def fun = {
        // Some code
    }
    val funLike = fun _
}
JAiro
źródło
2
To dobre wytłumaczenie, ale nie ma ich wszystkich. Brakuje zignorowanych parametrów / zmiennych, łączenia liter i interpunkcji, typów egzystencjalnych, typów o wyższym rodzaju
Owen
w twoim List(1,2,3,4,5).foreach(print(_))przypadku jest to o wiele bardziej czytelne List(1,2,3,4,5).foreach(print), wcale tak naprawdę nie potrzebujesz podkreślenia, ale myślę, że to tylko kwestia stylu
Electric Coffee
1
co powiesz na „_” działa jako symbol zastępczy w kolekcjach z funkcją .map, .flatten, .toList ...... Czasami sprawia to, że nie rozumiem. :(
m0z4rt
34

Jest jedno użycie, które widzę, że wszyscy tutaj zapomnieli wymienić ...

Zamiast tego:

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

Możesz po prostu to zrobić:

List("foo", "bar", "baz").map(_.toUpperCase())
Kawa elektryczna
źródło
więc _ tutaj działa jako przestrzeń nazw wszystkich dostępnych funkcji?
Crt,
2
@Crt nie, działa jak skrótn => n
Electric Coffee
2
czy nie jest to składnia zastępcza wymieniona w dwóch pierwszych odpowiedziach?
joelb
13

Oto kilka przykładów _użycia:

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)

We wszystkich powyższych przykładach jeden znak podkreślenia reprezentuje element na liście (w celu zmniejszenia pierwszy znak podkreślenia reprezentuje akumulator)

swaraj patil
źródło
11

Oprócz zastosowań , o których wspomniał JAiro, podoba mi się ten:

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

Jeśli ktoś potrzebuje wszystkich właściwości połączenia, może:

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

Jeśli potrzebujesz tylko hosta i portu, możesz:

val ( host, port, _, _ ) = getConnectionProps
Tolitius
źródło
0

Istnieje konkretny przykład użycia „_”:

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

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

może być równy:

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

Zastosowanie „_” w niektórych scenariuszach spowoduje automatyczną konwersję na „(x $ n) => x $ n”

Ke.Steve
źródło
czuję, że przykład każdego jest elementem iteracji, myślę, że jest to bardziej cukier niskopoziomowy, powiedział zwięzła konwersja lambda
Ke.Steve