У чому полягає вся користь підкреслення в Scala?


540

Я переглянув список опитувань, проведених на scala-lang.org, і помітив цікаве запитання: " Чи можете ви назвати всі способи використання" _ "? ". Ти можеш? Якщо так, будь ласка, зробіть це тут. Пояснюються приклади.



5
Дивіться також презентацію The Dreaded _ на Slideshare
Mifeet,

Відповіді:


576

Я можу придумати

Екзистенційні типи

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

Більш високі параметри типу

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

Ігноровані змінні

val _ = 5

Ігноровані параметри

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

Проігноровані імена самотипів

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

Шаблони підстановок

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

Шаблони підстановок в інтерполяціях

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

Послідовність підстановки у шаблонах

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

Імпорт wildcard

import java.util._

Приховування імпорту

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

Приєднання листів до операторів

def bang_!(x: Int) = 5

Оператори призначення

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

Синтаксис заповнювача

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

Значення методу

List(1, 2, 3) foreach println _

Перетворення параметрів виклику по імені у функції

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

Ініціалізатор за замовчуванням

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

Можливо, ще й інші я забув!


Приклад, що показує, чому foo(_)і чим foo _відрізняються:

Цей приклад походить від 0__ :

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

  val set: Set[_ => Unit]

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

У першому випадку process _являє собою метод; Скала приймає поліморфний метод і намагається зробити його мономорфним, заповнивши параметр типу, але розуміє, що не існує типу, який можна заповнити, Aякий дасть тип (_ => Unit) => ?(Екзистенціал _- це не тип).

У другому випадку process(_)- лямбда; коли пишеш лямбда без явного типу аргументу, Скала виводить тип із аргументу, який foreachочікує, і _ => Unit є типом (тоді як просто простий _), тому його можна замінити і зробити висновок.

Це, можливо, найсміливіша ханша у Скалі, з якою я коли-небудь стикався.

Зауважимо, що цей приклад складається в 2.13. Ігноруйте це так, як це було призначено для підкреслення.


4
Я думаю, що є два-три, які всі підпадають під наголосом підкреслення у відповідності шаблонів, але +1 для приєднання букв до пунктуації! :-)
Даніель К. Собрал

22
val x: Any = _
Джованні Ботта

2
@Owen Я не думаю, що println _ є частково застосованою функцією. Це ще один приклад синтаксису заповнювача, правильно? Значення карти (_ + 2) розширюється на щось схоже на карту (x => x + 2) так само, як pritnln (_) розширюється на щось схоже на карту (x => println (x))
Andrew Cassidy

7
@AndrewCassidy Насправді println _і println(_)різні. Це можна побачити, наприклад, у тому, що вони поводяться з екзистенціальними та поліморфними типами дещо по-різному. Прикладемо трохи прикладу.
Оуен

3
@AndrewCassidy Добре Я додав приклад.
Оуен

179

З (мій запис) у FAQ , який я точно не гарантую, що він буде повним (я додав дві записи лише два дні тому):

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

Це теж частина цього питання .


2
Ви можете додати var i: Int = _або окремий випадок узгодження шаблону val (a, _) = (1, 2)або окремий випадок відкинутого valfor (_ <- 1 to 10) doIt()
huynhjl

1
І def f: T; def f_=(t: T)комбо для створення змінного члена.
huynhjl

Зіставлення шаблонів уже охоплене, а _назви методів є обманом. Але, ну гаразд. Я просто сподіваюся, що хтось інший оновить FAQ ... :-)
Даніель К. Собрал

1
Можливо, ви сумуєте за цим. vertx.newHttpServer.websocketHandler (_. writeXml (html))
angelokh

@angelokh Це параметр анонімного заповнення функції, п’ятий у списку.
Даніель К. Собрал

84

Прекрасним поясненням використання підкреслення є магія Scala _ [підкреслення] .

Приклади:

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

У Scala _діє *під час імпорту пакетів подібний до Java.

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

У Scala геттер і сеттер буде визначено неявно для всіх не приватних варіантів об'єкта. Ім'я геттера те саме, що ім'я змінної та _=додається до імені встановлення.

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

Використання:

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

Якщо ви спробуєте призначити функцію новій змінній, функція буде викликана, а результат буде призначений змінній. Ця плутанина виникає через необов'язкові дужки для виклику методу. Ми повинні використовувати _ після імені функції, щоб призначити її іншій змінній.

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

2
Це хороше пояснення, але у них навіть немає всіх. У ньому відсутні ігноровані параметри / змінні, з'єднання букв та розділових знаків, екзистенційні типи, типи вищого роду
Owen

у вашому випадку List(1,2,3,4,5).foreach(print(_))це набагато легше читати List(1,2,3,4,5).foreach(print), вам навіть зовсім не потрібна підкреслення, але, мабуть, це лише питання стилю
Електрична кава

1
як щодо того, що "_" працює як власник місця у колекціях з функцією .map, .flatten, .toList ...... Колись це робить мене непорозумінням. :(
m0z4rt

34

Я можу побачити, що всі тут, здається, забули перелічити ...

Замість того, щоб робити це:

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

Ви можете просто зробити це:

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

так _ тут діє як простір імен усіх доступних функцій?
Crt

2
@Crt ні, це діє як скорочення дляn => n
Електрична кава

2
Хіба це не синтаксис заповнювача, який згадується у перших двох відповідях?
joelb

13

Ось ще кілька прикладів, де _використовується:

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)

У всіх наведених вище прикладах одна підкреслення являє собою елемент у списку (для зменшення першого підкреслення являє собою акумулятор)


11

Крім звичаїв, про які згадував Джайро, мені подобається таке:

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

Якщо комусь потрібні всі властивості з'єднання, він може:

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

Якщо вам потрібен лише хост і порт, ви можете зробити:

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

0

Існує конкретний приклад використання "_":

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

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

може дорівнювати:

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

Застосування "_" у деяких сценаріях автоматично перетвориться на "(x $ n) => x $ n"


відчуваю, що приклад кожного є елементом ітерації, я думаю, що це більше схоже на цукор синтаксису низького рівня, сказав лаконічне перетворення лямбда
Ke.Steve
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.