Scala: краткая форма сопоставления с шаблоном, которая возвращает логическое значение

Я часто писал что-то вроде этого:

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

Есть ли более короткий способ проверить, соответствует ли какое-либо значение шаблону? Я имею в виду, в этом случае я мог бы просто написать if (a == b) // do stuff, но что если шаблон более сложный? Например, при сопоставлении со списком или любым шаблоном произвольной сложности. Я хотел бы иметь возможность написать что-то вроде этого:

if (a matches b) // do stuff

Я относительно новичок в Scala, поэтому прошу прощения, если я что-то упустил большой:)

6 ответов

Решение

Именно поэтому я написал эти функции, которые, по-видимому, впечатляют, поскольку никто их не упомянул.

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

Это может помочь:

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

Теперь некоторые пояснения по общему характеру проблемы.

Где может состояться матч?

Есть три места, где может происходить сопоставление с образцом: val, case а также for, Правила для них:

// 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 который может проверить, может ли быть найдено совпадение, а также метод, называемый lift, который превратит PartialFunction[T, R] в 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]и второе возвращение Option[Seq[T]], где None означает, что совпадение не сделано, и Some(result) постараюсь соответствовать result как описано выше.

Таким образом, здесь есть всевозможные синтаксические альтернативы, которые просто невозможны без использования одной из трех конструкций, где может происходить сопоставление с образцом. Вы можете эмулировать некоторые функции, такие как равенство значений и экстракторы, но не все из них.

match Оператор в Scala является наиболее мощным при использовании в функциональном стиле. Это значит, а не "делать что-то" в 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 функциональным образом, потому что он также имеет возвращаемое значение...)

Шаблоны также могут быть использованы для выражений. Ваш пример кода

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

может быть выражено как

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

Хитрость заключается в том, чтобы обернуть его, чтобы сделать его действительным перечислителем. Например, список (а) также подойдет, но я думаю, что некоторые (а) наиболее близки к вашему предполагаемому значению.

Лучшее, что я могу придумать, это:

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

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

Это не принесет вам очков стиля.

Ответ Кима может быть "улучшен", чтобы лучше соответствовать вашему требованию:

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 часто и отходя от такого рода конструкций императивного стиля (следуя хорошему объяснению Мадока).

Другие вопросы по тегам