Tất cả các công dụng của dấu gạch dưới trong Scala là gì?


540

Tôi đã xem danh sách các cuộc khảo sát được thực hiện trên scala-lang.org và nhận thấy một câu hỏi tò mò: " Bạn có thể kể tên tất cả các cách sử dụng của ____ không? ". Bạn có thể? Nếu có, xin vui lòng làm như vậy ở đây. Ví dụ giải thích được đánh giá cao.


15
Tôi đã đọc bộ slide tuyệt vời này cách đây không lâu: Scala Dreaded Underscore
Dan Burton

Câu trả lời:


576

Những cái tôi có thể nghĩ là

Các loại hiện sinh

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

Thông số loại cao hơn

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

Các biến bị bỏ qua

val _ = 5

Bỏ qua các tham số

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

Bỏ qua tên của các loại tự

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

Mẫu ký tự đại diện

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

Các mẫu ký tự đại diện trong nội suy

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

Ký tự đại diện trong các mẫu

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

Nhập khẩu ký tự đại diện

import java.util._

Ẩn nhập khẩu

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

Tham gia gửi thư cho các nhà khai thác

def bang_!(x: Int) = 5

Toán tử chuyển nhượng

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

Cú pháp giữ chỗ

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

Giá trị phương thức

List(1, 2, 3) foreach println _

Chuyển đổi các tham số gọi theo tên thành các chức năng

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

Trình khởi tạo mặc định

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

Có thể có những người khác tôi đã quên!


Ví dụ cho thấy tại sao foo(_)foo _khác nhau:

Ví dụ này xuất phát từ 0__ :

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

  val set: Set[_ => Unit]

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

Trong trường hợp đầu tiên, process _đại diện cho một phương thức; Scala sử dụng phương thức đa hình và cố gắng biến nó thành đơn hình bằng cách điền vào tham số loại, nhưng nhận ra rằng không có loại nào có thể điền vào để Ađưa ra loại (_ => Unit) => ?(Hiện tại _không phải là loại).

Trong trường hợp thứ hai, process(_)là một lambda; khi viết lambda không có loại đối số rõ ràng, Scala sẽ nhập loại từ đối số foreachmong đợi và _ => Unit một loại (trong khi chỉ đơn giản _là không), vì vậy nó có thể được thay thế và suy ra.

Đây cũng có thể là gotcha khó nhất trong Scala tôi từng gặp.

Lưu ý rằng ví dụ này biên dịch trong 2.13. Bỏ qua nó như nó được gán cho gạch dưới.


4
Tôi nghĩ rằng có hai hoặc ba tất cả đều phù hợp với cách sử dụng gạch dưới trong khớp mẫu, nhưng +1 để nối các chữ cái để chấm câu! :-)
Daniel C. Sobral

22
val x: Any = _
Giovanni Botta

2
@Owen Tôi không nghĩ println _ là một chức năng được áp dụng một phần. Đó là một ví dụ khác về cú pháp giữ chỗ phải không? Ý nghĩa bản đồ (_ + 2) mở rộng thành một thứ tương tự như bản đồ (x => x + 2) giống như pritnln (_) mở rộng thành một thứ tương tự như bản đồ (x => println (x))
Andrew Cassidy

7
@AndrewCassidy Thực tế println _println(_)khác biệt. Bạn có thể thấy điều này ví dụ ở chỗ chúng xử lý các kiểu tồn tại và đa hình hơi khác nhau. Sẽ đưa ra một ví dụ trong một chút.
Owen

3
@AndrewCassidy OK Tôi đã thêm một ví dụ.
Owen

179

Từ (mục nhập của tôi) trong Câu hỏi thường gặp , mà tôi chắc chắn không đảm bảo hoàn thành (tôi đã thêm hai mục chỉ hai ngày trước):

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

Đây cũng là một phần của câu hỏi này .


2
Có thể bạn có thể thêm var i: Int = _hoặc trường hợp đặc biệt của khớp mẫu val (a, _) = (1, 2)hoặc trường hợp đặc biệt của val bị loại bỏfor (_ <- 1 to 10) doIt()
huynhjl

1
def f: T; def f_=(t: T)combo để tạo thành viên f có thể thay đổi.
huynhjl

Khớp mẫu đã được trình bày và _trên tên phương thức là gian lận. Nhưng, tốt, ok. Tôi chỉ hy vọng người khác cập nhật Câu hỏi thường gặp ... :-)
Daniel C. Sobral

1
Có thể bạn bỏ lỡ cái này vertx.newHttpServer.websocketHandler (_. writeXml (html))
angelokh

@angelokh Đó là tham số giữ chỗ chức năng ẩn danh, thứ năm trong danh sách.
Daniel C. Sobral

84

Một lời giải thích tuyệt vời về việc sử dụng dấu gạch dưới là phép thuật Scala _ [gạch dưới] .

Ví dụ:

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

Trong Scala, _hoạt động tương tự như *trong Java trong khi nhập các gói.

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

Trong Scala, một getter và setter sẽ được định nghĩa ngầm cho tất cả các vars không riêng tư trong một đối tượng. Tên getter giống như tên biến và _=được thêm cho tên setter.

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

Sử dụng:

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

Nếu bạn cố gắng gán một hàm cho một biến mới, hàm sẽ được gọi và kết quả sẽ được gán cho biến đó. Sự nhầm lẫn này xảy ra do các dấu ngoặc tùy chọn cho việc gọi phương thức. Chúng ta nên sử dụng _ sau tên hàm để gán nó cho một biến khác.

class Test {
    def fun = {
        // Some code
    }
    val funLike = fun _
}

2
Đó là một lời giải thích tốt, nhưng nó thậm chí không có tất cả chúng. Nó thiếu các tham số / biến bị bỏ qua, nối các chữ cái và dấu chấm câu, các kiểu tồn tại, các loại được phân loại cao hơn
Owen

trong việc bạn List(1,2,3,4,5).foreach(print(_))dễ đọc hơn nhiều List(1,2,3,4,5).foreach(print), bạn thậm chí không thực sự cần gạch dưới, nhưng tôi đoán đó chỉ là vấn đề về phong cách
Cà phê điện

1
làm thế nào về "_" hoạt động như người giữ chỗ trong Bộ sưu tập với chức năng .map, .flatten, .toList ...... Đôi khi, nó làm tôi hiểu lầm. :(
m0z4rt

34

Có một cách sử dụng tôi có thể thấy mọi người ở đây dường như đã quên liệt kê ...

Thay vì làm điều này:

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

Bạn có thể chỉ cần làm điều này:

List("foo", "bar", "baz").map(_.toUpperCase())

vậy _ ở đây hoạt động như một không gian tên của tất cả các hàm có sẵn?
Crt

2
@ Không, nó hoạt động như một cách viết tắt chon => n
Cà phê điện

2
Đây không phải là cú pháp giữ chỗ được đề cập trong hai câu trả lời hàng đầu sao?
tham gia

13

Dưới đây là một số ví dụ khác _được sử dụng:

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)

Trong tất cả các ví dụ trên, một dấu gạch dưới đại diện cho một yếu tố trong danh sách (để giảm dấu gạch dưới đầu tiên đại diện cho bộ tích lũy)


11

Bên cạnh những công dụng mà JAiro đã đề cập, tôi thích cái này:

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

Nếu ai đó cần tất cả các thuộc tính kết nối, anh ta có thể làm:

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

Nếu bạn chỉ cần một máy chủ và một cổng, bạn có thể làm:

val ( host, port, _, _ ) = getConnectionProps

0

Có một ví dụ cụ thể rằng "_" được sử dụng:

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

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

có thể bằng:

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

Áp dụng ứng dụng của _ _ trong một số tình huống sẽ tự động chuyển đổi sang tên (x $ n) => x $ n tựa


Cảm thấy ví dụ của mọi người là một yếu tố của phép lặp, tôi nghĩ rằng nó giống như một cú pháp đường cấp thấp, chuyển đổi súc tích lambda cho biết
Ke.Steve
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.