Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scala, C# equivalent of F# active patterns

Tags:

c#

scala

f#

F# has a feature enabling users to extend pattern matching:

let (|Even|Odd|) n = if n % 2 = 0 then Even else Odd
match 3 with | Odd -> printfn "odd"
             | Even -> printfn "even"

or:

(** 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)

The magic happens in the ParseRegex pattern which is defined as such:

(** 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])

The ParseRegex can now be used everywhere and the whole point of it is that parsing out parts of strings can be done in a very concise way.

Does Scala have a similar feature?

In C# this level of conciseness seems hard to achieve but maybe some clever trick using implicit conversion or so could help?

like image 796
Martin Konicek Avatar asked Jan 13 '11 18:01

Martin Konicek


4 Answers

Disclaimer: I don't know F#.

In scala, if a value has a method named unapply (or unapplySeq), then it will be used in pattern matching.

The definition of unapply is:

object Example {
    def unapply(t: T): Option[(T1, ..., Tn)] = ...
}

If None is return it means no match, Some((t1, ..., tn)) means a match is successful and can be bound to values t1 to tn

And then it can be used as:

value match {
  case Example(t1, ..., tn) => ...
}

If the matching can have dynamic number of results, then unapplySeq is used which returns Option[List[T]]

Finally, if no variables need to be bound when matching, unapply can just return a Boolean

Examples:

val Date = ""(\d{1,2})/(\d{1,2})/(\d{1,2})$".r

"12/12/12" match {
   case Date(m, d, y) => ...
}

The .r method turns a string into a Regex. That class has an unapplySeq method which matches the groups in the regex.

case class Person(name: String, age: Int)

somePerson match {
    case Person(name, age) => ...
}

In this case, the syntax case class creates an singleton object with the same name (Person) with an unapply method matching the constructor arguments.

UPDATE: Here's how to define Even and Odd First, some generalization. Both Even and Odd can be expressed via a function, but we need to make this function conform to the extractors spec, that is, apply it via unapply

class BooleanExtractor[T](f: T => Boolean) { 
  def unapply(t: T) = f(t)
}

Now, we can use like this (showing two ways)

val Even = new BooleanExtractor[Int](_ % 2 == 0)
object Odd extends BooleanExtractor[Int](_ % 2 == 1)

And use:

scala> 3 match {
     |   case Even() => println("even")
     |   case Odd() => println("odd")
     | }
odd
like image 169
IttayD Avatar answered Nov 10 '22 11:11

IttayD


You can achieve this functionality by way of a scala feature called Extractors.

For your even/odd example:

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")
}
like image 33
nullspace Avatar answered Nov 10 '22 09:11

nullspace


You can achieve the same in Scala. The concept is called Extractors. The syntax to define them is a bit uglier than in F# it seems. I'll provide the first example:

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

Your second example works, too. You have to return the DateTime objects from the unapply method. I provide a link here where you can read more on the topic.

like image 26
ziggystar Avatar answered Nov 10 '22 10:11

ziggystar


I noticed that no one added C# code for this, so I made an attempt of reproducing the feature in code here: https://siderite.dev/blog/c-equivalent-to-f-active-patterns.html Basically I created one or two helper classes that then would allow me to write code like this:

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");
like image 24
Siderite Zackwehdex Avatar answered Nov 10 '22 09:11

Siderite Zackwehdex