I want to strengthen a pattern to match only numbers which pass an additional validation function.
let (|IsValid|_|) n = ...
let (|Nil|One|Two|) (l : int list) =
match l with
| a :: b :: t -> Two(a + b)
| a :: t -> One(a)
| _ -> Nil
The 'One' case is easy:
| IsValid(a) :: t -> One(a)
The 'Two' case isn't obvious to me. It needs to validate the sum of the numbers. Can I do this without using a when-guard?
...
Edit: I could use a when-guard (with a bool-returning isValid function) like this:
| a :: b :: t when isValid a + b -> Two(a + b)
This is less elegant than just matching a pattern; worse, a + b is applied twice.
Also note this is a simplified version of my actual code (I'm not trying to simply match against different lengths of list for example) - the question is about nested matching over the double cons pattern.
-
When you do:
| a :: b :: t -> ...
You aren't necessarily matching two elements in a list. It's better to use
[]
instead oft
to match two elements exactly --t
can be a list of more elements.| a :: b :: [] -> Two (a+b)
This will ensure that you are matching two and only two elements --error checking for free! I suggest doing this even though you expect the function to only accept a list of 0, 1, or 2 elements. So,
EDIT:
let (|MatchTwo|_|) = function | a :: b :: t -> Some(a + b :: t) | _ -> None let (|Nil|One|Two|) (l : int list) = match l with | MatchTwo(IsValid(a) :: t) -> Two(a) | IsValid(a) :: t -> One(a) | _ -> Nil
Yeah, use
when
. This is a mess. Pattern matching is just that, applying functions within the match really doesn't make sense. But take into account what I've mentioned prior. Based on your example:match l with | a :: b :: t when isValid (a+b) -> Two (a+b) | a :: t when isValid (a) -> One a | _ -> Nil
The second pattern will match lists of length longer then one if isValid is false on the first pattern --be warned. Be as specific in your patterns as possible, if you mean to match one element, do it.
If whatever operation you use to combine a and b (in this case +) is computationally expensive, then you will have to drop the
when
and use a let statement before testing the isValid and returning the variant type.Pete Montgomery : Thanks, but I've maybe simplified my real code badly. I actually don't want to match a list with only 0, 1 or 2 elements. But this is still completely irrelevant to the question!Pete Montgomery : Sorry, I didn't mean to sound so terse in my last comment! Thanks for taking your time to help me. -
My solution: Add a "helper" recogniser with a return value designed to be used in the parent pattern:
let (|MatchTwo|_|) = function | a :: b :: t -> Some(a + b :: t) | _ -> None
Use it like so:
let (|Nil|One|Two|) (l : int list) = match l with | MatchTwo(IsValid(a) :: t) -> Two(a) | IsValid(a) :: t -> One(a) | _ -> Nil
0 comments:
Post a Comment