The Fsharpx.Extras NuGet package exposes an active pattern for regular expression matching, qualified as Fsharpx.Text.Regex.Match.
The first parameter is a RegexOptions value from the BCL.
Rather than having to write:
let someFunc =
| Match RegexOptions.None "...pattern 1..." matches -> ...
| Match RegexOptions.None "...pattern 2..." matches -> ...
| Match RegexOptions.None "...pattern 3..." matches -> ...
...
I was hoping it would be possible to instead have (using a revised Match' active pattern):
let someFunc =
| Match' "...pattern 1..." matches -> ...
| Match' "...pattern 2..." matches -> ...
| Match' "...pattern 3..." matches -> ...
...
One possible definition of Match' I came up with was:
let (|Match'|_|) pattern =
function
| Match RegexOptions.None pattern matches -> Some matches
| _ -> None
...which works fine. However, I couldn't help wonder if there was another approach similar to a partially applied function such as:
let (|Match'|_|) =
Match RegexOptions.None
Frustratingly, this complains about Type has no accessible object constructors..
Is something similar to the latter (alebit failing) approach possible?
Open the Regex module, then change your last example to
let (|Match|_|) = (|Match|_|) RegexOptions.None
In fact, if you look at the source code, you'll see an example of this in the Compiled module.
https://github.com/fsprojects/FSharpx.Extras/blob/master/src/FSharpx.Extras/Regex.fs
Related
I have the following active pattern:
let (|IgnoreCase|_|) (arg: string) (input: string)
if String.Equals(argument, input, StringComparison.OrdinalIgnoreCase)
then Some()
else None
Normally, you would have to use this pattern individually for each case and do something like this:
function
| IgnoreCase "string1"
| IgnoreCase "string2"
What I would like to do is be able to use other matching expressions with the active pattern, for example:
function
| IgnoreCase ("string1" | "string2") // OR pattern
| IgnoreCase ("string1" & "string2") // AND pattern
That returns the error "Invalid argument to parameterized pattern label". I know you can do that using discriminated unions, so it seems like it should be possible using active patterns.
The way you've written the active pattern, it takes a string argument and checks if the input matches the string - this way, there is no way of turning the argument into a pattern itself.
In this particular case, & does not really make sense - but if you just wanted to support |, you could pass a list of arguments:
let (|IgnoreCase|_|) (args: string list) (input: string) =
if args |> List.exists (fun arg ->
String.Equals(arg, input, StringComparison.OrdinalIgnoreCase))
then Some() else None
function
| IgnoreCase ["string1"; "string2"] -> 1
| _ -> 0
If you wanted something more sophisticated, you would need to structure the logic so that the active pattern returns something that can be further matched. One option might be to have pattern that turns the input into lower-case, because then you can specify composed pattern for the lower-cased string:
let (|AsLowerCase|_|) (input: string) =
Some(input.ToLowerInvariant())
function
| AsLowerCase("string1" | "string2") -> 1
| AsLowerCase("string1" & "string2") -> 1
| _ -> 0
This makes the syntax with & syntactically valid, but it will never match. You could however have other patterns like StartsWith and EndsWith so this would then make sense, e.g. to write AsLowerCase(StartsWith "foo" & EndsWith "bar")
I created a discriminated union which has three possible options:
type tool =
| Hammer
| Screwdriver
| Nail
I would like to match a single character to one tool option. I wrote this function:
let getTool (letter: char) =
match letter with
| H -> Tool.Hammer
| S -> Tool.Screwdriver
| N -> Tool.Nail
Visual Studio Code throws me now the warning that only the first character will be matched and that the other rules never will be.
Can somebody please explain this behaviour and maybe provide an alternative?
That's not how characters are denoted in F#. What you wrote are variable names, not characters.
To denote a character, use single quotes:
let getTool (letter: char) =
match letter with
| 'H' -> Tool.Hammer
| 'S' -> Tool.Screwdriver
| 'N' -> Tool.Nail
Apart from the character syntax (inside single quotes - see Fyodor's response), you should handle the case when the letter is not H, S or N, either using the option type or throwing an exception (less functional but enough for an exercise):
type Tool =
| Hammer
| Screwdriver
| Nail
module Tool =
let ofLetter (letter: char) =
match letter with
| 'H' -> Hammer
| 'S' -> Screwdriver
| 'N' -> Nail
| _ -> invalidArg (nameof letter) $"Unsupported letter '{letter}'"
Usage:
> Tool.ofLetter 'S';;
val it : Tool = Screwdriver
> Tool.ofLetter 'C';;
System.ArgumentException: Unsupported letter 'C' (Parameter 'letter')
Let us have a type definition for a tree with several types of binary nodes, among other types of nodes, i.e.
type Tree =
| BinaryNodeA of Tree * Tree
| BinaryNodeB of Tree * Tree
| [Other stuff...]
I want to manipulate this tree using a recursive function that could, e.g., swap subnodes of any kind of binary node (by constructing a new node). The problem that is driving me crazy: How to match all BinaryNodes so that Node flavor becomes "a parameter" so as to have generic swap that can be applied to any BinaryNode flavor to return swapped node of that flavor?
I know how to match all Trees that are BinaryNodes by using an active pattern:
let (|BinaryNode|_|) (tree : Tree) =
match tree with
| BinaryNodeA _ | BinaryNodeB _ -> Some(tree)
| _ -> None
But that's not good enough because the following does not seem achievable:
match tree with
| [cases related to unary nodes..]
| BinaryNode a b -> BinaryNode b a
In other words, I have not found way to use BinaryNode flavor as if it were parameter like a and b. Instead, it seems I have to match each BinaryNode flavor separately. This could have practical significance if there were large number of binary node flavors. Type Tree is AST for Fsyacc/Fslex-generated parser/lexer, which limits options to restructure it. Any ideas?
You just need to change the definition of your active pattern:
type Flavor = A | B
let (|BinaryNode|_|) (tree : Tree) =
match tree with
| BinaryNodeA(x,y) -> Some(A,x,y)
| BinaryNodeB(x,y) -> Some(B,x,y)
| _ -> None
let mkBinaryNode f t1 t2 =
match f with
| A -> BinaryNodeA(t1,t2)
| B -> BinaryNodeB(t1,t2)
Then you can achieve what you want like this:
match tree with
| [cases related to unary nodes..]
| BinaryNode(f,t1,t2) -> mkBinaryNode f t2 t1
But if this is a common need then it might make sense to alter the definition of Tree to include flavor instead of dealing with it using active patterns.
Here is my problem: I'm trying to write a parser leveraging the power of active patterns in F#. The basic signature of a parsing function is the following
LazyList<Token> -> 'a * LazyList<Token>
Meaning it takes a lazy list of tokens, and returns the result of the parse and the new list of tokens after parsing, so as to follow functional design.
Now, as a next step, I can define active patterns that will help me match some constructs directly in match expressions, thusly
let inline (|QualName|_|) token_stream =
match parse_qualified_name token_stream with
| Some id_list, new_stream -> Some (id_list, new_stream)
| None, new_stream -> None
let inline (|Tok|_|) token_stream =
match token_stream with
| Cons (token, tail) -> Some(token.variant, tail)
| _ -> None
and then match parse results in a high level fashion this way
let parse_subprogram_profile = function
| Tok (Kw (KwProcedure | KwFunction),
QualName(qual_name,
Tok (Punc (OpeningPar), stream_tail))) as token_stream ->
// some code
| token_stream -> None, token_stream
The problem I have with this code is that every new matched construct is nested, which is not readable, especially if you have a long chain of results to match. I'd like to have the ability to define a matching operator such as the :: operator for list, which would enable me to do the following :
let parse_subprogram_profile = function
| Tok (Kw (KwProcedure | KwFunction)) ::
QualName(qual_name) ::
Tok (Punc (OpeningPar)) :: stream_tail as token_stream ->
// some code
| token_stream -> None, token_stream
But I don't think such a thing is possible in F#. I would even accept a design in which I have to call a specific "ChainN" active pattern where N is the number of element I want to parse, but I don't know how to design such a function if it is possible.
Any advice or directions regarding this ? Is there an obvious design I didn't see ?
I had something like this in mind, too, but actually gave up going for this exact design. Something you can do is to use actual lists.
In such case, you would have a CombinedList which is made of (firstly) a normal list acting as a buffer and (secondly) a lazy list.
When you want to match against a pattern, you can do:
match tokens.EnsureBuffer(4) with
| el1 :: el2 :: remaining -> (el1.v+el2.v, tokens.SetBuffer(remaining))
| el3 :: el4 :: el5 :: el6 :: remaining -> (el1.v-el2.v+el3.v-el4.v, tokens.SetBuffer(remaining))
where EnsureBuffer and SetBuffer may either mutate "tokens" and return it or return it if no change are required or return a new instances otherwise.
Would that solve your problem?
François
Given the following active pattern:
let (| HasMatch |) (x:string) =
if x.Contains("0") then Some()
else None;;
And the following pattern matching func:
let testFn x = function
| HasMatch i -> printfn "%A" i
| _ -> printf "nope";;
The last line's wildcard pattern says warning FS0026: This rule will never be matched
All of the examples i see seem to infer that partial active patterns must return Some('a) to match, and that ones that return None get captured by the wildcard. The error seems to say differently.
What am i missing?
I think you should add the None case to the active pattern declaration as follows:
let (| HasMatch | _ |) (x:string) =
if x.Contains("0") then Some()
else None;;
In your orignal example, the compiler infers that you actually want to return the Option type. When you run the printf in your example, you would see it print Some Null when there is a match.
Also, it is bad to return Some(), you should return say Some(x) or similar