Scala: краткая форма сопоставления с шаблоном, которая возвращает логическое значение - PullRequest
41 голосов
/ 14 декабря 2010

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

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

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

if (a matches b) // do stuff

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

Ответы [ 6 ]

62 голосов
/ 14 декабря 2010

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

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
12 голосов
/ 14 декабря 2010

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

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

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

12 голосов
/ 14 декабря 2010

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

7 голосов
/ 14 декабря 2010

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

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

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

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

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

5 голосов
/ 14 декабря 2010

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

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

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

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

3 голосов
/ 14 декабря 2010

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

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

...