web-dev-qa-db-ja.com

Scala:ブール値を返す短い形式のパターンマッチング

私は自分がこのようなことをかなり頻繁に書いていることに気づきました:

_a match {     
  case `b` => // do stuff
  case _ => // do nothing
}
_

ある値がパターンに一致するかどうかを確認する短い方法はありますか?つまり、この場合はif (a == b) // do stuffと書くことができますが、パターンがもっと複​​雑な場合はどうでしょうか。リストまたは任意の複雑さのパターンと照合する場合のように。私はこのようなものを書くことができるようにしたいと思います:

_if (a matches b) // do stuff
_

私はScalaに比較的慣れていないので、何か大きなものが足りない場合はご容赦ください:)

42

これがまさに私がこれらの関数を書いた理由ですが、誰も言及していないため、明らかに印象的にあいまいです。

scala> import PartialFunction._
import PartialFunction._

scala> cond("abc") { case "def" => true }
res0: Boolean = false

scala> condOpt("abc") { case x if x.length == 3 => x + x }
res1: Option[Java.lang.String] = Some(abcabc)

scala> condOpt("abc") { case x if x.length == 4 => x + x }
res2: Option[Java.lang.String] = None
64
psp

Scalaのmatch演算子は、関数型スタイルで使用すると最も強力です。つまり、caseステートメントで「何かをする」のではなく、次のようになります。有用な値。命令型スタイルの例を次に示します。

var value:Int = 23
val command:String = ... // we get this from somewhere
command match {
  case "duplicate" => value = value * 2
  case "negate" => value = -value
  case "increment" => value = value + 1
  // etc.
  case _ => // do nothing
}
println("Result: " + value)

上記の「何もしない」は余計に見えるので少し痛いのはよくわかります。ただし、これは上記が命令型で書かれているためです。このような構成が必要になる場合もありますが、多くの場合、コードを機能スタイルにリファクタリングできます。

val value:Int = 23
val command:String = ... // we get this from somewhere
val result:Int = command match {
   case "duplicate" => value * 2
   case "negate" => -value
   case "increment" => value + 1
   // etc.
   case _ => value
}
println("Result: " + result)

この場合、たとえば変数に割り当てることができる値として、matchステートメント全体を使用します。また、matchステートメントがどのような場合でも値を返さなければならないこともはるかに明白です。最後のケースが欠落している場合、コンパイラーは単に何かを補うことができませんでした。

これは好みの問題ですが、一部の開発者は、このスタイルがより現実的な例でより透過的で扱いやすいと考えています。 Scalaプログラミング言語の発明者は、matchのより機能的な使用を念頭に置いていたに違いありません。実際、ifステートメントの方が理にかなっています。特定のアクションを実行する必要があるかどうかを判断するだけで済みます(一方、戻り値もあるため、ifを機能的に使用することもできます...)

12
Madoc

これは役立つかもしれません:

_class Matches(m: Any) {
    def matches[R](f: PartialFunction[Any, R]) { if (f.isDefinedAt(m)) f(m) }
}
implicit def any2matches(m: Any) = new Matches(m)

scala> 'c' matches { case x: Int => println("Int") }                                

scala> 2 matches { case x: Int => println("Int") }  
Int
_

ここで、問題の一般的な性質について説明します。

どこで試合が起こりますか?

パターンマッチングが発生する可能性のある場所は、valcase、およびforの3つです。それらのルールは次のとおりです。

_// throws an exception if it fails
val pattern = value 

// filters for pattern, but pattern cannot be "identifier: Type",
// though that can be replaced by "id1 @ (id2: Type)" for the same effect
for (pattern <- object providing map/flatMap/filter/withFilter/foreach) ...

// throws an exception if none of the cases match
value match { case ... => ... }
_

ただし、caseが表示される可能性がある別の状況があります。これは、関数リテラルと部分関数リテラルです。例えば:

_val f: Any => Unit = { case i: Int => println(i) }
val pf: PartialFunction[Any, Unit] = { case i: Int => println(i) }
_

関数と部分関数の両方が、caseステートメントのいずれにも一致しない引数で呼び出された場合に例外をスローします。ただし、部分関数には、一致するかどうかをテストできるisDefinedAtというメソッドと、_PartialFunction[T, R]_をに変換するliftというメソッドもあります。 _Function[T, Option[R]]_。これは、値が一致しない場合、例外をスローする代わりにNoneになることを意味します。

マッチとは何ですか?

一致は、多くの異なるテストの組み合わせです。

_// assign anything to x
case x

// only accepts values of type X
case x: X

// only accepts values matches by pattern
case x @ pattern

// only accepts a value equal to the value X (upper case here makes a difference)
case X

// only accepts a value equal to the value of x
case `x`

// only accept a Tuple of the same arity
case (x, y, ..., z)

// only accepts if extractor(value) returns true of Some(Seq()) (some empty sequence)
case extractor()

// only accepts if extractor(value) returns Some something
case extractor(x)

// only accepts if extractor(value) returns Some Seq or Tuple of the same arity
case extractor(x, y, ...,  z)

// only accepts if extractor(value) returns Some Tuple2 or Some Seq with arity 2
case x extractor y

// accepts if any of the patterns is accepted (patterns may not contain assignable identifiers)
case x | y | ... | z
_

現在、エクストラクタはメソッドunapplyまたはunapplySeqであり、最初はBooleanまたは_Option[T]_を返し、2番目は_Option[Seq[T]]_を返します。ここでNoneは一致が行われないことを意味し、Some(result)は上記のようにresultと一致しようとします。

したがって、ここにはあらゆる種類の構文上の選択肢がありますが、パターンの一致が発生する可能性のある3つの構造のいずれかを使用しないと不可能です。値の同等性や抽出機能など、一部の機能をエミュレートできる場合がありますが、すべてではありません。

12

パターンは、式にも使用できます。コードサンプル

a match {     
  case b => // do stuff
  case _ => // do nothing
}

その後、次のように表すことができます

for(b <- Some(a)) //do stuff

秘訣は、をラップして有効な列挙子にすることです。例えば。 List(a)も機能しますが、Some(a)は意図した意味に最も近いと思います。

7
Maarten Bynens

私が思いつくことができる最高のものはこれです:

def matches[A](a:A)(f:PartialFunction[A, Unit]) = f.isDefinedAt(a)

if (matches(a){case ... =>}) {
    //do stuff
}

ただし、これではスタイルポイントは獲得できません。

5
Kim Stebel

キムの答え 要件に合わせて「改善」することができます。

class AnyWrapper[A](wrapped: A) {
  def matches(f: PartialFunction[A, Unit]) = f.isDefinedAt(wrapped)
}
implicit def any2wrapper[A](wrapped: A) = new AnyWrapper(wrapped)

その後:

val a = "a" :: Nil
if (a matches { case "a" :: Nil => }) {
  println("match")
}

しかし、私はそれをしません。 => }) {シーケンスはここでは本当に醜く、コード全体は通常の一致よりもはるかに明確ではありません。さらに、暗黙的な変換を検索するコンパイル時のオーバーヘッドと、一致をPartialFunctionでラップする実行時のオーバーヘッドが発生します(他の定義済みのmatchesメソッド(String)のようなメソッド。

少し見栄えを良くする(そして冗長性を減らす)ために、この定義をAnyWrapperに追加できます。

def ifMatch(f: PartialFunction[A, Unit]): Unit = if (f.isDefinedAt(wrapped)) f(wrapped)

次のように使用します。

a ifMatch { case "a" :: Nil => println("match") }

これにより、case _ =>行が節約されますが、単一のステートメントではなくブロックが必要な場合は、二重中括弧が必要です...それほど良くありません。

この構成は、副作用のあるものを実行するためにのみ使用できるため、実際には関数型プログラミングの精神ではないことに注意してください。関数が部分的であるため、値を返すためにそれを簡単に使用することはできません(したがって、Unit戻り値)—デフォルト値が必要です。または、Optionインスタンスを返すことができます。 。しかし、ここでも、おそらくマッチでそれをアンラップするので、何も得られません。

率直に言って、これらのmatchを頻繁に見て使用することに慣れ、この種の命令型構造から離れたほうがよいでしょう(以下 マドックの素晴らしい説明 )。