Подтвердить что ты не робот

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

Я довольно часто писал что-то подобное:

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

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

if (a matches b) // do stuff

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

4b9b3361

Ответ 1

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

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

Ответ 2

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

Ответ 3

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

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, как описано выше.

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

Ответ 4

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

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

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

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

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

Ответ 5

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

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

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

Это не выиграет вас ни в каких точках стиля.

Ответ 6

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

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

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