Scala, эквивалент С# активных шаблонов F #
F # имеет функцию, позволяющую пользователям расширять сопоставление шаблонов:
let (|Even|Odd|) n = if n % 2 = 0 then Even else Odd
match 3 with | Odd -> printfn "odd"
| Even -> printfn "even"
или
(** Parsing date from any of the formats: MM/DD/YY, MM/DD/YYYY, YYYY-MM-DD *)
let parseDate = function
| ParseRegex "(\d{1,2})/(\d{1,2})/(\d{1,2})$" [Integer m; Integer d; Integer y]
-> new DateTime(y + 2000, m, d)
| ParseRegex "(\d{1,2})/(\d{1,2})/(\d{3,4})" [Integer m; Integer d; Integer y]
-> new DateTime(y, m, d)
| ParseRegex "(\d{1,4})-(\d{1,2})-(\d{1,2})" [Integer y; Integer m; Integer d]
-> new DateTime(y, m, d)
Магия происходит в шаблоне ParseRegex, который определяется как таковой:
(** Definition of the MatchRegex active pattern *)
let (|ParseRegex|_|) rgx s = match Regex(rgx).Match(s) with
| m when not m.Success -> None
| m -> Some(List.tail [for x in m.Groups->x.Value])
Теперь ParseRegex можно использовать повсюду, и вся его точка состоит в том, что разбор частей строк может быть выполнен очень кратким образом.
Есть ли у Scala аналогичная функция?
В С# этот уровень лаконичности кажется труднодоступным, но, может быть, какой-то хитроумный трюк, использующий неявное преобразование, может помочь?
Ответы
Ответ 1
Отказ от ответственности: я не знаю F #.
В scala, если значение имеет метод с именем unapply (или unapplySeq), то он будет использоваться при сопоставлении с образцом.
Определение unapply:
object Example {
def unapply(t: T): Option[(T1, ..., Tn)] = ...
}
Если None
возвращает, это означает отсутствие соответствия, Some((t1, ..., tn))
означает, что совпадение выполнено успешно и может быть привязано к значениям t1-tn
И затем его можно использовать как:
value match {
case Example(t1, ..., tn) => ...
}
Если совпадение может иметь динамическое число результатов, используется unapplySeq
, который возвращает Option[List[T]]
Наконец, если никакие переменные не должны быть связаны при сопоставлении, unapply может просто вернуть логическое
Примеры:
val Date = ""(\d{1,2})/(\d{1,2})/(\d{1,2})$".r
"12/12/12" match {
case Date(m, d, y) => ...
}
Метод .r
превращает строку в Regex. Этот класс имеет метод unapplySeq, который соответствует группам в регулярном выражении.
case class Person(name: String, age: Int)
somePerson match {
case Person(name, age) => ...
}
В этом случае синтаксис case class
создает одноэлементный объект с тем же именем (Person) с методом unapply, соответствующим аргументам конструктора.
UPDATE: здесь, как определить Even и Odd
Во-первых, некоторое обобщение. Как Even, так и Odd могут быть выражены через функцию, но мы должны сделать эту функцию соответствующей спецификации экстракторов, то есть применить ее через unapply
class BooleanExtractor[T](f: T => Boolean) {
def unapply(t: T) = f(t)
}
Теперь мы можем использовать это (показано двумя способами)
val Even = new BooleanExtractor[Int](_ % 2 == 0)
object Odd extends BooleanExtractor[Int](_ % 2 == 1)
И используйте:
scala> 3 match {
| case Even() => println("even")
| case Odd() => println("odd")
| }
odd
Ответ 2
Вы можете достичь этой функциональности с помощью функции scala, называемой Extractors.
Для вашего четного/нечетного примера:
object Even {
def unapply(x:Int) = if (x % 2 == 0) Some(x) else None
}
object Odd {
def unapply(x:Int) = if (x % 2 == 1) Some(x) else None
}
3 match {
case Even(x) => println("even")
case Odd(x) => println("odd")
}
Ответ 3
Вы можете добиться того же результата в Scala. Эта концепция называется Extractors. Синтаксис для их определения немного уродливее, чем в F #. Я приведу первый пример:
scala> object Even {def unapply(z: Int) = (z%2 == 0)}
defined module Even
scala> object Odd {def unapply(z: Int) = (z%2 != 0)}
defined module Odd
scala> 2 match {
| case Even() => "even";
| case Odd() => "odd";
| }
res10: java.lang.String = even
Ваш второй пример тоже работает. Вы должны вернуть объекты DateTime из метода unapply. Я предоставляю ссылку здесь, где вы можете прочитать больше по теме.
Ответ 4
Я заметил, что для этого не добавлен код С#, поэтому я попытался воспроизвести эту функцию в коде здесь: http://siderite.blogspot.com/2016/03/c-equivalent-to-f-active-patterns.html
В основном я создал один или два вспомогательных класса, которые затем позволили бы мне написать такой код:
var apInt = Option<int>.From<string>(s =>
{
int i;
return System.Int32.TryParse(s, out i)
? new Option<int>(i)
: Option<int>.Empty;
});
var apBool = Option<bool>.From<string>(s =>
{
bool b;
return System.Boolean.TryParse(s, out b)
? new Option<bool>(b)
: Option<bool>.Empty;
});
var testParse = new Action<string>(s =>
{
FluidFunc
.Match(s)
.With(apInt, r => Console.WriteLine($"The value is an int '{r}'"))
.With(apBool, r => Console.WriteLine($"The value is an bool '{r}'"))
.Else(v => Console.WriteLine($"The value '{v}' is something else"));
});
testParse("12");
testParse("true");
testParse("abc");