Scalaでのアンダースコア(_)の使用法
1. 概要
アンダースコア(_)は、Scalaで広く使用されている記号の1つです。 コードが非常に単純で短くなるため、シンタックスシュガーと呼ばれることもあります。 しかし、これはしばしば多くの混乱を招き、学習曲線を増加させます。
このチュートリアルでは、Scalaでのアンダースコアのさまざまで最も一般的な使用法を見ていきます。
2. パターンマッチングとワイルドカード
アンダースコアは、ワイルドカードとして、また未知のパターンのマッチングに広く使用されています。 これは、おそらく、Scalaを学習するときに遭遇するアンダースコアの最初の使用法です。 いくつかの例を見てみましょう。
2.1. モジュールのインポート
パッケージをインポートするときにアンダースコアを使用して、モジュールのすべてまたは一部のメンバーをインポートする必要があることを示します。
// imports all the members of the package junit. (equivalent to wildcard import in java using *)
import org.junit._
// imports all the members of junit except Before.
import org.junit.{Before => _, _}
// imports all the members of junit but renames Before to B4.
import org.junit.{Before => B4, _}
2.2. 存在するタイプ
また、アンダースコアをワイルドカードとして使用して、 List、Array、Seq、Option、、Vectorなどのタイプクリエーターのすべてのタイプに一致させます。
def getLength(x : List[List[_]]): Int = x.length
assertEquals(getLength(List(List(8), List("str"))), 2)
assertEquals(getLength(List(List(5.00), List("str"))), 2)
assertEquals(getLength(List(List(Array(7)), List("str"))), 2)
_を使用すると、内部リストですべてのタイプの要素を許可しました。
2.3. マッチング
match キーワードを使用すると、アンダースコアを使用して、定義されたケースで処理されない可能性のあるすべてのケースをキャッチできます。
たとえば、アイテムの価格を考えると、特定の特別価格に基づいてアイテムを購入または販売したいと考えています。 価格が130なら買いたいのですが、150なら売りたいです。 これら以外の価格については、アイテムの承認を取得する必要があります。
def itemTransaction(price: Double): String = {
price match {
case 130 => "Buy"
case 150 => "Sell"
// if price is not any of 130 and 150, this case is executed
case _ => "Need approval"
}
}
itemTransaction(130) // Buy
itemTransaction(150) // Sell
itemTransaction(70) // Need approval
itemTransaction(400) // Need approval
その他の例については、パターンマッチングチュートリアルを読むこともできます。
3. 物事を無視する
アンダースコアは、コード内のどこにも使用されていない変数と型を無視するために使用できます。
3.1. 無視されたパラメータ
たとえば、関数の実行では、アンダースコアを使用して、使用されていないパラメータを非表示にすることができます:
val ints = (1 to 4).map(_ => "Int")
assertEquals(ints, Vector("Int", "Int", "Int", "Int"))
アンダースコアを使用して、map無名関数にある値をすべて無視しました。 範囲の各要素に対してIntを返すだけです。 匿名化されたパラメーターを関数のプレースホルダーとして使用できます。 これにより、コードがクリーンになります。 あまり明確ではありませんが:
val prices = Seq(10.00, 23.38, 49.82)
val pricesToInts = prices.map(_.toInt)
assertEquals(pricesToInts, Seq(10, 23, 49))
ここで、マッピングは次と同等です。
prices.map(x => x.toInt)
アンダースコアを使用してネストされたコレクションにアクセスすることもできます:
val items = Seq(("candy", 2, true), ("cola", 7, false), ("apple", 3, false), ("milk", 4, true))
val itemsToBuy = items
.filter(_._3) // filter in only available items (true)
.filter(_._2 > 3) // filter in only items with price greater than 3
.map(_._1) // return only the first element of the tuple; the item name
assertEquals(itemsToBuy, Seq("milk"))
パターンマッチングにはすでに隠れたインポートがあります。 それはまた、物事を無視するものとして参照することができます。 特定のモジュールを無視して、残りをインポートできます。
import org.junit.{Before => _, _}
これにより、 Before を除く(無視)junitパッケージのすべてのメンバーがインポートされました。
3.2. 無視された変数
構築されたエントリの変数を気にしない場合は、アンダースコアを使用して無視できます。
たとえば、分割文字列の最初の要素のみが必要です。
val text = "a,b"
val Array(a, _) = text.split(",")
assertEquals(a, "a")
2番目のものだけが必要な場合も同じことが当てはまります。
val Array(_, b) = text.split(",")
assertEquals(b, "b")
これを3つ以上のエントリに拡張できます。
val text = "a,b,c,d,e"
val Array(a, _*) = text.split(",")
assertEquals(a, "a")
最初のエントリの後の残りのエントリを無視するには、*とともにアンダースコアを使用します。
不要なエントリにアンダースコアを使用してランダムに無視することもできます。
val Array(a, b, _, d, e) = text.split(",")
assertEquals(a, "a")
assertEquals(b, "b")
assertEquals(d, "d")
assertEquals(e, "e")
3.3. デフォルト値への変数の初期化
変数の初期値が不要な場合は、デフォルトとしてアンダースコアを使用できます。
var x: String = _
x = "real value"
println(x) // real value
これはローカル変数では機能しません。 ローカル変数は初期化する必要があります。
4. コンバージョン
いくつかの方法で、変換にアンダースコアを使用できます。
4.1. 機能の再割り当て(イータ拡張)
アンダースコアを使用すると、メソッドを関数に変換できます。
def multiplier(a: Int, b: Int): Int = a*b
val times = multiplier _ // reassign multiplier to times
assertEquals(multiplier(8, 13), times(8, 13))
4.2. 可変引数シーケンス
seqName:_ * (タイプascriptionの特別なインスタンス)を使用して、シーケンスを変数引数に変換できます。
たとえば、varargs( Int 型)を受け入れてすべてを合計する独自のsum関数を定義しましょう。
def sum(args: Int*): Int = {
args.reduce(_ + _)
}
val sumable = Seq(4, 5, 10, 3)
val sumOfSumable = sum(sumable: _*) // convert the sequence sumable to varargs using sumable: _*
assertEquals(sumOfSumable, 22)
4.3. 部分的に適用された機能
関数に一部の引数のみを指定し、残りは渡されるようにすることで、部分的に適用された関数を生成できます。
提供されていないパラメーターは、アンダースコアに置き換えられます。
def sum2(x:Int,y:Int): Int = x+y
val sumToTen = sum(10,_:Int)
val sumFiveAndTen = sumToTen(5)
assertEquals(sumFiveAndTen, 15)
部分的に適用された関数でのアンダースコアの使用は、物事を無視するとしてグループ化することもできます。 複数のパラメーターグループを持つ関数のパラメーターグループを無視して、ある種の部分的に適用された関数を生成することもできます。
def bar(x:Int,y:Int)(z:String,a:String)(b:Float,c:Float): Int = x
val foo = bar(1,2) _
assertEquals(foo("Some string", "Another string")(3/5, 6/5), 1)
4.4. 代入演算子(セッターのオーバーライド)
デフォルトのセッターをオーバーライドすることは、アンダースコアを使用した一種の変換と見なすこともできます。
class Product {
private var a = 0
def price = a
def price_=(i: Int): Unit = {
require(i > 10)
a = i
}
}
val product = new Product
product.price = 20
assertEquals(product.price, 20)
try {
product.price = 7 // will fail because 7 is not greater than 10
fail("Price must be greater than 10")
} catch {
case _: IllegalArgumentException => assertNotEquals(product.price, 7)
}
5. その他の使用法
どのグループにも当てはまらない可能性のあるアンダースコアの他の使用法があります。
5.1. オペレーター/句読点への文字の結合
英数字のように変数名に句読点を使用することはできません。 変数名に句読点を使用するとより明確になる場合は、文字を句読点にアンダースコアで結合することでそれを行うことができます。
def list_++(list: List[_]): List[_] = List.concat(list, list)
val concatenatedList = list_++(List(2, 5))
assertEquals(concatenatedList, List(2, 5, 2, 5))
5.2. 数値リテラルセパレーター(Scala 2.13+)
Scalaは、Scala2.13でアンダースコアを使用した数値リテラルセパレーターを導入しました。 いくつかの例を見てみましょう:
var x = 1_000_000 // 1000000
x = 1_00_00_00 // 1000000
x = 1_0000_00 // 1000000
var pi = 3_14e-0_2 // 3.14
pi = 3_14e-02 // 3.14
pi = 314e-0_2 // 3.14
pi = 314e-02 // 3.14
5.3. 高等種タイプ
Higher-Kindedタイプは、あるタイプを抽象化し、次に別のタイプを抽象化するタイプです。 このようにして、Scalaは型コンストラクター間で一般化できます。 実在型とよく似ています。
アンダースコアを使用して、より種類の多いタイプを定義できます。
trait ObjectContainer[T[_]] { // higher-kinded type parameter
def checkIfEmpty(collection: T[_]): Boolean
}
object SeqContainer extends ObjectContainer[Seq] {
override def checkIfEmpty(collection: Seq[_]): Boolean = !collection.nonEmpty
}
var seqIsEmpty = SeqContainer.checkIfEmpty(Seq(7, "7"))
assertTrue(seqIsEmpty == false)
seqIsEmpty = SeqContainer.checkIfEmpty(Seq())
assertTrue(seqIsEmpty == true)
6. 結論
このチュートリアルでは、Scalaアンダースコアを使用するさまざまな方法を見てきました。
いつものように、記事の完全なソースコードは、GitHubでから入手できます。