F# optional pattern matching - f#

Right now I have the following pattern match
let argList = args |> List.ofSeq
match argList with
| "aaa" :: [] -> run "aaa"
| "bbb" :: DateTimeExact "yyyyMMdd" date :: [] -> run "bbb" date
....
It's used to parse command line parameters like
exec aaa
exec bbb 20141101
Now I want to be able to add option -o (optional). Such as exec bbb 20141101 -o. How to modify the pattern to do it? Even better, -o should be able to be place in any position.

If you're looking for an approach that can be easily extended, I find that using the F# type system can really help when interpreting command line arguments.
Ultimately, you want to end up with the details of what was provided in the argList. So first, define the type(s) that you want. You're then parsing the list of strings into those type(s). In your example, it appears that you can either have "aaa", or "bbb" that is immediately followed by a date. That feels like a Union type:
type Cmd =
| Aaa
| Bbb of DateTime
Also, there may be an extra flag passed "-o" that can appear at any point (except between bbb and its date). So that feels like a Record type:
type Args = { Cmd: Cmd; OSet: bool; }
So now we know that we want to turn the argList collection into an instance of Args.
You're wanting to go through each of the items in the argList and handle them. One way to do this is with recursion. You want to match on each item (or pair of items, or triple, etc) before continuing to check the rest of the list. At each match, you update* the Args to include the new details.
(* Because Args is immutable, you don't actually update it. A new instance is created.)
let rec parseArgs' argList (a: Args) =
match argList with
| [] ->
a
| "aaa"::xs ->
parseArgs' xs { a with Cmd = Aaa }
| "bbb":: DateTimeExact "yyyyMMdd" d ::xs ->
parseArgs' xs { a with Cmd = Bbb(d) }
| "-o"::xs ->
parseArgs' xs { a with OSet = true }
| x::_ ->
failwith "Invalid argument %s" x
When calling parseArgs', you need to provide an initial version of Args - this is your "default" value.
let parseArgs argList = parseArgs' argList { Cmd = Aaa; OSet = false }
And then you can use it:
parseArgs ["aaa"] // valid
parseArgs ["bbb"; "20141127"] // valid
parseArgs ["bbb"; "20141127";"-o"] // valid
parseArgs ["-o";"bbb"; "20141127"] // valid
parseArgs ["ccc"] // exception
Your Args instance now has all the details in a strongly typed form.
let { Cmd = c; OSet = o } = parseArgs ["aaa"]
match c with
| Aaa -> run "aaa" o
| Bbb d -> run "bbb" d o
As you want more and different options, you just add them to your Types, then update your match statement to handle whatever the input version is.
There are of course many different ways to handle this. You might was error messages rather than exceptions. You might want the Cmd value to be an Option rather than defaulting to "Aaa". Etc, etc.
Edit in response to comment:
Adding an additional -p someValue is straightforward.
First, update Args to hold the new data. In your example the value of "someValue" is the important bit, but it may or may not be provided. And it's defined by "-p" so that we know it when we see it. For simplicity I'm going to pretend someValue is a string. So Args becomes:
type Args = { Cmd: Cmd; OSet: bool; PValue: string option }
Once you add the new field in Args, your "default" should complain because the new field is not set. I'm going to say that by default, -p is not set (that's why I've used a string option). So update to:
let parseArgs argList = parseArgs' argList { Cmd = Aaa; OSet = false; PValue = None }
Then you just need to identify and capture the value when it's provided. This is the pattern matching part:
let rec parseArgs' argList (a: Args) =
match argList with
... snip ...
| "-p"::p::xs ->
parseArgs' xs { a with PValue = Some p}
... snip ...
Then just use the PValue value when you have your Args item.
Note: I'm not doing much validation here - I'm just assuming that whatever comes after "-p" is the value I want. You could add validation during the pattern matching using a "when" guard, or validate the Args value after it has been created. How far you go is up to you. I usually think about my audience (just me vs. work colleagues vs. whole internet).
Whether it's a good idea to allow the "bbb" and the date to be specified separately is up to you as the designer, but if the date should only be specified with the "bbb" command and is mandatory for it, then it probably makes sense to keep them together.
If the Cmd and the date were not "tightly linked" you could pattern match for the date separately from the cmd. If so, you might move the date from being held on the Cmd union, to being a field in its own right in Args.
If date was optional, you might use a -d [date] option instead. This would keep the same pattern as the other optional arguments.
An important thing to aim for is to try to make your interface as intuitive as possible for people to use. This is mostly about being "predictable". It doesn't necessarily mean catering for as many input styles as possible.

Related

F# pattern matching with optional list of tuples

I'm trying to use pattern matching for an optional list of tuples but I could not write an exhaustive matching expression despite trying everything I can think of.
I'm struggling to understand why the F# compiler is insisting that my patterns in the following examples are not exhaustive.
module Mapper.PatternMatchingOddity
type A = A of string
type B = B of string
type ProblemType = ProblemType of (A * B) list option
//Incomplete pattern matches on this expression. Some ([_;_]) may indicate a case...
let matchProblem = function
|Some [(x:A,y:B)] -> []
|Some ([_,_]) -> [] //rider says this rule will never be matched
|None -> []
//same as before
let matchProblem1 = function
|Some [_,_] -> []
|Some [] -> []
//|Some _ -> []//this removes the warning but what is the case not covered by the previous two?
|None -> []
let matchProblem2 (input:ProblemType) =
match input with //same as before
|ProblemType (Some [(x:A,y:B)]) -> []
|ProblemType None -> []
How do I write the exhaustive matching and what am I missing above? Can you give an example for an input that would be accepted as a valid parameter to these functions and slip through the patterns?
Great question! I think many people that start out with F# grapple with how lists, options and tuples interact. Let me start by saying: the compiler is correct. The short answer is: you are only matching over singleton lists. Let me try to explain that a little deeper.
Your type is ('a * 'b) list option, essentially. In your case, 'a and 'b are themselves a single-case discriminated using of a string. Let's simplify this a bit and see what happens if we look at each part of your type in isolation (you may already know this, but it may help to put it in context):
First of all, your type is option. This has two values, None or Some 'a. To match over an option you can just do something like
match o with
| Some value -> value
| None -> failwith "nothing"`
Next, your type is a list. The items in a list are divided by semicolons ;. An empty list is [], a singleton list (one with a single item) is [x] and multiple items [x;y...]. To add something to the start of a list use ::. Lists are a special type of discriminated union and the syntax to match over them mimics the syntax of lists construction:
match myList with
| [] -> "empty"
| [x] -> printfn "one item: %A" x
| [x; y] -> printfn "two items: %A, %A" x y
| x::rest -> printfn "more items, first one: %A" x
Third, your list type is itself a tuple type. To deconstruct or match over a tuple type, you can use the comma ,, as with match (x, y) with 1, 2 -> "it's 1 and 2!" ....
Combine all this, we must match over an option (outer) then list (middle) then tuple. Something like Some [] for an empty list and None for the absence of a list and Some [a, b] for a singleton list and Some (a,b)::rest for a list with one or more items.
Now that we have the theory out of the way, let's see if we can tackle your code. First let's have a look at the warning messages:
Incomplete pattern matches on this expression. Some ([_;_]) may indicate a case...
This is correct, the item in your code is separated by , denoting the tuple, and the message says Some [something; something] (underscore means "anything"), which is a list of two items. But it wouldn't help you much to add it, because the list can still be longer than 2.
rider says this rule will never be matched
Rider is correct (which calls the FSC compiler services underneath). The rule above that line is Some [(x:A,y:B)] (the :A and :B are not needed here), which matches any Some singleton array with a tuple. Some [_,_] does the same, except that it doesn't catch the values in a variable.
this removes the warning but what is the case not covered by the previous two?
It removes the warning because Some _ means Some with anything, as _ means just that: it is a placeholder for anything. In this case, it matches the empty list, the 2-item list, the 3-item list the n-item list (the only one your match is the 1-item list in that example).
Can you give an example for an input that would be accepted as a valid parameter
Yes. Valid input that you were not matching is Some [] (empty list), Some [A "a", B "x"; A "2", B "2"] (list of two items) etc.
Let's take your first example. You had this:
let matchProblem = function
|Some [(x:A,y:B)] -> [] // matching a singleton list
|Some ([_,_]) -> [] // matches a singleton list (will never match, see before)
|None -> [] // matches None
Here's what you (probably) need:
let notAProblemAnymore = function
// first match all the 'Some' matches:
| Some [] -> "empty" // an empty list
| Some [x,y] -> "singleton" // a list with one item that is a tuple
| Some [_,a;_,b] -> "2-item list" // a list with two tuples, ignoring the first half of each tuple
| Some ((x,y)::rest) -> "multi-item list"
// a list with at least one item, and 'rest' as the
// remaining list, which can be empty (but won't,
// here it has at least three items because of the previous matches)
| None -> "Not a list at all" // matching 'None' for absence of a list
To sum it up: you were matching over a list that had only one item and the compiler complained that you missed lists of other lengths (empty lists and lists that have more than one item).
Usually it is not necessary to use option with a list, because the empty list already means the absence of data. So whenever you find yourself writing the type option list consider whether just list would suffice. It will make the matching easier.
You are struggling because your example is too “example”.
Let’s convert your example to a more meaningful one: check the input, so that
If it is none then print “nothing”, otherwise:
If it has zero element then print “empty”
If it has only one element then print “ony one element: ...”
If it has two elements then print “we have two elements: ...”
If it has three elements then print “there are three elements: ...”
If it has more than three elements then print “oh man, the first element is ..., the second element is ..., the third element is ..., and N elements more”
Now you can see that your code only covers the first 3 cases. So the F# compiler was correct.
To rewrite the code:
let matchProblem (ProblemType input) =
match input with
| None -> printfn "nothing"
| Some [] -> ...
| Some [(x, y)] -> ...
| Some [(x1, y1); (x2, y2)] -> ...
| Some [(x1, y1); (x2, y2); (x3, y3)] -> ...
| Some (x1, y1) :: (x2, y2) :: (x3, y3) :: rest -> // access rest.Length to print the number of more elements
Notice that I’m using pattern matching on the parameter ProblemType input so that I can extract the input in a convenient way. This makes the later patterns simpler.
Personally, when I learned F#, I didn’t understand many features/syntax until I used them in production code.

F# is it possible to "upcast" discriminated union value to a "superset" union?

Let's say there are two unions where one is a strict subset of another.
type Superset =
| A of int
| B of string
| C of decimal
type Subset =
| A of int
| B of string
Is it possible to automatically upcast a Subset value to Superset value without resorting to explicit pattern matching? Like this:
let x : Subset = A 1
let y : Superset = x // this won't compile :(
Also it's ideal if Subset type was altered so it's no longer a subset then compiler should complain:
type Subset =
| A of int
| B of string
| D of bool // - no longer a subset of Superset!
I believe it's not possible to do but still worth asking (at least to understand why it's impossible)
WHY I NEED IT
I use this style of set/subset typing extensively in my domain to restrict valid parameters in different states of entities / make invalid states non-representable and find the approach very beneficial, the only downside is very tedious upcasting between subsets.
Sorry, no
Sorry, but this is not possible. Take a look at https://fsharpforfunandprofit.com/posts/fsharp-decompiled/#unions — you'll see that F# compiles discriminated unions to .NET classes, each one separate from each other with no common ancestors (apart from Object, of course). The compiler makes no effort to try to identify subsets or supersets between different DUs. If it did work the way you suggested, it would be a breaking change, because the only way to do this would be to make the subset DU a base class, and the superset class its derived class with an extra property. And that would make the following code change behavior:
type PhoneNumber =
| Valid of string
| Invalid
type EmailAddress =
| Valid of string
| ValidButOutdated of string
| Invalid
let identifyContactInfo (info : obj) =
// This came from external code we don't control, but it should be contact info
match (unbox obj) with
| :? PhoneNumber as phone -> // Do something
| :? EmailAddress as email -> // Do something
Yes, this is bad code and should be written differently, but it illustrates the point. Under current compiler behavior, if identifyContactInfo gets passed a EmailAddress object, the :? PhoneNumber test will fail and so it will enter the second branch of the match, and treat that object (correctly) as an email address. If the compiler were to guess supersets/subsets based on DU names as you're suggesting here, then PhoneNumber would be considered a subset of EmailAddress and so would become its base class. And then when this function received an EmailAddress object, the :? PhoneNumber test would succeed (because an instance of a derived class can always be cast to the type of its base class). And then the code would enter the first branch of the match expression, and your code might then try to send a text message to an email address.
But wait...
What you're trying to do might be achievable by pulling out the subsets into their own DU category:
type AorB =
| A of int
| B of string
type ABC =
| AorB of AorB
| C of decimal
type ABD =
| AorB of AorB
| D of bool
Then your match expressions for an ABC might look like:
match foo with
| AorB (A num) -> printfn "%d" num
| AorB (B s) -> printfn "%s" s
| C num -> printfn "%M" num
And if you need to pass data between an ABC and an ABD:
let (bar : ABD option) =
match foo with
| AorB data -> Some (AorB data)
| C _ -> None
That's not a huge savings if your subset has only two common cases. But if your subset is a dozen cases or so, being able to pass those dozen around as a unit makes this design attractive.

FParsec and pipe3 make the arguments explicit or add a type notation

I am trying to use the pipe3 function from the FParsec library but I get an error I don't know how to solve.
Given the Record
type Point = { x: float; y: float }
and the following parser
let plistoffloats' =
pipe3 pfloat (pchar ',' .>> spaces) pfloat
(fun first z second -> { x = first; y = second })
What I try to achieve is a parser that receives a string in format "1.1, 3.7" and returns a Point
run plistoffloats' "1.1, 3.7"
Input : "1.1, 3.7"
Desired output : Point = {x = 1.1; y = 3.7;}
Error :
error FS0030: Value restriction. The value 'plistoffloats'' has been inferred to have generic type
val plistoffloats' : Parser <Point,'__a>
Either make the arguments to 'plistoffloats'' explicit or, if you do not intend for it to be generic, add a type annotation.
A simpler example with pchar also didn't work.
let parsesA = pchar 'a'
error FS0030: Value restriction. The value 'parsesA' has been inferred to have generic type
val parsesA : Parser<char,'_a>
Either make the arguments to 'parsesA' explicit or, if you do not intend for it to be generic, add a type annotation.
This is covered in the FParsec documentation; it will happen with any parser. The reason is because in the .Net type system, functions are allowed to be generic, but values are not — and in FParsec, you're generally defining parsers as values (e.g., you're typically writing let psomething = ... where psomething takes no parameters). Read the linked documentation page for the whole explanation — I won't copy and paste the whole thing — but the short version is that you can do one of two things:
Create a test function that looks like the following, and make sure it's used within the same source file on your parser:
let test p str =
match run p str with
| Success(result, _, _) -> printfn "Success: %A" result
| Failure(errorMsg, _, _) -> printfn "Failure: %s" errorMsg
Annotate your parser with a type annotation like the following:
type UserState = unit // You might change this later
let plistoffloats' : Parser<_, UserState> =
// ...
It sounds like you're trying to do #1, but unless your parser is called with test plistoffloats' in the same source file, the F# type inference won't be able to infer your user state type and will give you that error.
P.S. You can read more about the F# value restriction error here: Understanding F# Value Restriction Errors
P.P.S. The _ in the first position of Parser<_, UserState> does not mean "This type could be anything" the way _ means in other contexts like pattern matching. Instead, _ in a type annotation means "Please infer this type for me so that I don't have to specify it explicitly". In FParsec contexts, this is very useful because all your parsers will have UserState as their second type argument, but will have a varying type for the first type argument. And since the first type argument is the one that the type inference can infer, it means that you can copy and paste the type Parser<_, UserState> to all your parsers and F# will Do The Right Thing™ in each case.

Does F# naming conventions forbids having the same name inside different disc. union types?

I have a common type in one module and that type is used inside two other discriminated union types.
I named them with the same name because of convenience. Other names are different. Next thing, I am trying to make a helper that prints types in console.
The first line fails to compile because of the type mismatch inside the match-case. I've tried a few things and it still fails with or without opening modules, forcing type and so on.
The other thing is, if I change the manes to Common1 and Common2 it works without any problems.
I remember reading that types with the same signatures are stored inside the same internal type, but my real-life example has different signatures and still fails.
Am I missing a point somewhere?
Example:
Example fails to compile with error:
Error This expression was expected to have type OneA but here has type OneB
module commonThings =
type CommonThing =
| Comm1 of int
| Comm2 of int
module thingA =
open commonThings
type OneA =
| A1 of string
| Common of CommonThing
| A2 of string
module thingB =
open commonThings
type OneB =
| B1 of string
| Common of CommonThing
| B2 of string
module printAB =
open commonThings
open thingA
open thingB
let printA (msg:OneA) =
match msg with
| A1 v -> printfn "A1"
| Common v -> printfn "Common"
| A2 v -> printfn "A2"
module main =
[<EntryPoint>]
let main argv =
printfn "%A" argv
0 // return an integer exit code
When you open thingB module, type OneB comes into scope and the Common case label from type OneA gets shadowed with the one from type OneB.
When name clashes between types or union/active pattern cases occur, the most recent one wins. Reordering the opens would make it work by chance:
open thingB
open thingA
The right solution is to prefix the case name. There's also RequireQualifiedAccess attribute you can use to force a type (or module) to always require prefixes for its internals.
You can disambiguate by prefixing the type name:
let printA (msg:OneA) =
match msg with
| A1 v -> printfn "A1"
| OneA.Common v -> printfn "Common"
| A2 v -> printfn "A2"

F# Partial Active Pattern Matching "Rule Will Never Be Matched"

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

Resources