I have problem with pattern matching not compiling when it's expected
Current simplified version looks like this
match smth with
| pattern1 when isSomething pattern1 ->
doWork()
| pattern2 when isSomething pattern2 ->
doWork()
| _ -> () // do nothing
Problem here is that there is duplication of body in different cases
I've tried to use OR pattern to transform it to this
match smth with
| pattern1 when isSomething pattern1
| pattern2 when isSomething pattern2 ->
doWork()
| _ -> () // do nothing
But it gives Unexpected symbol '|' in pattern matching. Expected '->' or other token. F# Compiler 10
Attempts to surround isSomething with parens doesn't help. How to avoid duplication in that case?
Original code
match state.SearchOptions with
| { Pattern = empty } when String.IsNullOrEmpty empty ->
ValueSome (i, p, state.SelectedConfiguration, state.Solution.Configurations)
| { Pattern = pattern; IsRegex = true; IsCaseSensitive = true } when Regex.isMatch p.Name pattern ->
ValueSome (i, p, state.SelectedConfiguration, state.Solution.Configurations)
| { Pattern = pattern; IsRegex = true; IsCaseSensitive = false } when Regex.isMatchCaseInsensitive p.Name pattern ->
ValueSome (i, p, state.SelectedConfiguration, state.Solution.Configurations)
| { Pattern = pattern; IsRegex = false; IsCaseSensitive = true } when p.Name.Contains(pattern, StringComparison.InvariantCulture) ->
ValueSome (i, p, state.SelectedConfiguration, state.Solution.Configurations)
| { Pattern = pattern; IsRegex = false; IsCaseSensitive = false } when p.Name.Contains(pattern, StringComparison.InvariantCultureIgnoreCase) ->
ValueSome (i, p, state.SelectedConfiguration, state.Solution.Configurations)
| _ -> ValueNone
As mentioned in the comment, this cannot be done - the when clause applies to the whole clause. I think there are three alternative approaches.
First of all, you could define the result value as a variable before the match expression and then just return this from all four cases (as suggested in the comment).
Second, you could actually write this as a plain if expression:
let so = state.SearchOptions
if String.IsNullOrEmpty so.Pattern then
ValueSome (i, p, state.SelectedConfiguration, state.Solution.Configurations)
elif ( so.IsRegex && so.IsCaseSensitive &&
Regex.isMatch p.Name so.Pattern )
|| ( so.IsRegex && not so.IsCaseSensitive &&
Regex.isMatchCaseInsensitive p.Name so.Pattern )
|| ( not so.IsRegex && so.IsCaseSensitive &&
p.Name.Contains(so.Pattern, StringComparison.InvariantCulture) )
|| ( not so.IsRegex && not so.IsCaseSensitive &&
p.Name.Contains(so.Pattern, StringComparison.InvariantCultureIgnoreCase) ) then
ValueSome (i, p, state.SelectedConfiguration, state.Solution.Configurations)
else ValueNone
Another option would be to use active patterns - that way, you can integrate the conditions into the pattern itself. Something like this should do the trick:
let (|RegexMatch|_|) input pattern =
if Regex.isMatch input pattern then Some() else None
let (|RegexMatchInsensitive|_|) input pattern =
if Regex.isMatchCaseInsensitive input pattern then Some() else None
let (|Contains|_|) (input:string) pattern
input.Contains(pattern, StringComparison.InvariantCulture)
let (|ContainsInsensitive|_|) (input:string) pattern
input.Contains(pattern, StringComparison.InvariantCultureIgnoreCase)
match state.SearchOptions with
| { Pattern = empty } when String.IsNullOrEmpty empty ->
ValueSome (i, p, state.SelectedConfiguration, state.Solution.Configurations)
| { Pattern = Match p.Name; IsRegex = true; IsCaseSensitive = true }
| { Pattern = RegexMatchInsensitive p.Name; IsRegex = true; IsCaseSensitive = false }
| { Pattern = Contains p.Name; IsRegex = false; IsCaseSensitive = true }
| { Pattern = ContainsInsensitive p.Name; IsRegex = false; IsCaseSensitive = false } ->
ValueSome (i, p, state.SelectedConfiguration, state.Solution.Configurations)
| _ -> ValueNone
If I was doing this in just one place, I would probably go with either option one or two (depending on what you find more readable). If you are doing more things like this, the third option is a nice alternative.
Related
I'm trying to substitute types in a F# Expr, before converting it to an Expression for consumption by a c# lib.
But upon the call to LeafExpressionConverter.QuotationToExpression I receive the error
InvalidOperationException: The variable 't' was not found in the translation context
Basically I'm trying to substitute the equivalent of
<# fun (t: Record) -> t.A = 10 #> to
<# fun (t: Dict) -> t["A"] = 10 #>
Here is the code
type Record = {
A: int
}
type Dict () = //this is the type the c# lib wants (a dictionary representation of a type)
inherit Dictionary<string, obj>()
let substitute<'a> (ex: Expr<'a->bool>) =
let replaceVar (v: Var) = if v.Type = typeof<'a> then Var(v.Name, typeof<Dict>) else v
let tEntityItem = typeof<Dict>.GetProperty("Item")
let isATypeShapeVar = function | ShapeVar var -> var.Type = typeof<'a> | _ -> false
let rec substituteExpr =
function
| PropertyGet(exOpt, propOrValInfo, c) ->
match exOpt with
| None -> Expr.PropertyGet(propOrValInfo)
| Some ex ->
let args = c |> List.map substituteExpr
let newex = substituteExpr ex
match isATypeShapeVar ex with
| true ->
let getter = Expr.PropertyGet(newex, tEntityItem, [Expr.Value(propOrValInfo.Name)] )
Expr.Coerce(getter, propOrValInfo.PropertyType)
| false -> Expr.PropertyGet(newex, propOrValInfo, args)
| ShapeVar var -> Expr.Var (var |> replaceVar)
| ShapeLambda (var, expr) -> Expr.Lambda(var |> replaceVar, substituteExpr expr)
| ShapeCombination(shapeComboObject, exprList) ->
RebuildShapeCombination(shapeComboObject, List.map substituteExpr exprList)
substituteExpr ex |> LeafExpressionConverter.QuotationToExpression
substitute<Record> (<# fun t -> t.A = 10 #>)
I suspect I've missed something in the substitution, but I'm stumped as to what.
The the .ToString() result of the substituted F# Expr is
Lambda (t,
Call (None, op_Equality,
[Coerce (PropertyGet (Some (t), Item, [Value ("A")]), Int32),
Value (10)]))
which looks correct. And other than the coersion, is the equivalent of <# fun (t: Dict) -> t["A"] = 10 #>.ToString()
Why is the QuotationToExpression failing ?
Every time you call replaceVar, you return a different instance of Var. So when you replace the lambda parameter, it's one instance of Var, and later, when you replace newex, that's another instance of Var.
Lambda (t, Call (None, op_Equality, [Coerce (PropertyGet (Some (t), ... ))
^ ^
| |
---------------------------------------------------------
These are different `t`, unrelated, despite the same name
To make this work, you have to make it the same t. The dumbest, most straightforward way would be this:
let substitute<'a> (ex: Expr<'a->bool>) =
let newArg = Var("arg", typeof<Dict>)
let replaceVar (v: Var) = if v.Type = typeof<'a> then newArg else v
...
This will make your particular example work as expected, but it is still unsound, because you're replacing not just specifically the lambda parameter, but any variable of the same type. Which means that if the expression happens to contain any variables of the same type as the parameter, you'd still hit the same problem. For example, try converting this:
<# fun t -> let z = { A = 15 } in z.A = 15 && t.A = 10 #>
You'll get a similar error, but this time complaining about variable z.
A better way would be to maintain a map of variable substitutions as you go, insert new variables as you encounter them for the first time, but get them from the map on subsequent encounters.
An alternative approach would be to fish out specifically the lambda parameter and then replace only it, rather than comparing variable types.
But then there's the next level of weirdness: you're converting any property accessor to an indexer accessor, but in my example above, z.A shouldn't be thus converted. So you have to somehow recognize whether the object of property access is in fact the argument, and that may not be as trivial.
If you're willing to settle for just the case of t.A and fail on more complicated cases like (if true then t else t).A, then you can just match on the lambda argument and pass through any other expression:
let substitute<'a> (ex: Expr<'a->bool>) =
let arg =
match ex with
| ShapeLambda (v, _) -> v
| _ -> failwith "This is not a lambda. Shouldn't happen."
let newArg = Var("arg", typeof<Dict>)
let replaceVar (v: Var) = if v = arg then newArg else v
let tEntityItem = typeof<Dict>.GetProperty("Item")
let isATypeShapeVar = function | ShapeVar var -> var.Type = typeof<'a> | _ -> false
let rec substituteExpr =
function
| PropertyGet(Some (ShapeVar a), propOrValInfo, c) when a = arg ->
let getter = Expr.PropertyGet(Expr.Var newArg, tEntityItem, [Expr.Value(propOrValInfo.Name)] )
Expr.Coerce(getter, propOrValInfo.PropertyType)
| ShapeVar var -> Expr.Var (var |> replaceVar)
| ShapeLambda (var, expr) -> Expr.Lambda(var |> replaceVar, substituteExpr expr)
| ShapeCombination(shapeComboObject, exprList) ->
RebuildShapeCombination(shapeComboObject, List.map substituteExpr exprList)
| ex -> ex
substituteExpr ex |> LeafExpressionConverter.QuotationToExpression
> substituteExpr <# fun t -> let z = { A = 15 } in z.A = 15 && t.A = 10 #>
val it: System.Linq.Expressions.Expression =
ToFSharpFunc(arg => z => ((z.A == 15) AndAlso (Convert(arg.get_Item("A"), Int32) == 10)).Invoke(new Record(15)))
I'm pretty new to F#, and I'm trying to use recursion to solve a problem.
The function receives a string, and returns a bool. The string gets parsed, and evaluated. This is bool logic, so
(T|F) returns true
(T&(T&T)) returns true
((T|T)&(T&F)) returns false
(F) = returns false
My idea was that every time I found a ), replace the part of the string from the previous ( to that ) with the result of the Comparison match. Doing this over and over until only T or F remains, to return true or false.
EDIT:
I expect it to take the string, and keep swapping out what is in between the ( and ) with the result of the comparison until it comes down to a T or F. What is happening, is an error about an incomplete structured construct. The error is in the for loop.
As I am so new to this language, I'm not sure what I'm doing wrong. Do you see it?
let ComparisonSolver (comp:string) =
let mutable trim = comp
trim <- trim.Replace("(", "")
trim <- trim.Replace(")", "")
match trim with
| "T" -> "T"
| "F" -> "F"
| "!T" -> "F"
| "!F" -> "T"
| "T&T" -> "T"
| "F&F" -> "T"
| "T&F" -> "F"
| "F&T" -> "F"
| "T|T" -> "T"
| "F|F" -> "F"
| "T|F" -> "T"
| "F|T" -> "T"
| _ -> ""
let rec BoolParser arg =
let mutable args = arg
if String.length arg = 1 then
match arg with
| "T" -> true
| "F" -> false
else
let mutable ParseStart = 0
let endRange = String.length args
for letter in [0 .. endRange]
if args.[letter] = "(" then
ParseStart <- letter
else if args.[letter] = ")" then
args <- args.Replace(args.[ParseStart .. letter], ComparisonSolver args.[ParseStart .. letter])
BoolParser args
let result = BoolParser "(T)&(F)"
There are a few things you need to correct.
for letter in [0 .. endRange] is missing a do at the end of it - it should be for letter in [0 .. endRange] do
The if comparisons in the for loop are comparing chars with strings. You need to replace "(" and ")" with '(' and ')'
for letter in [0 .. endRange] will go out of range: In F# the array construct [x..y] will go from x to y inclusive. It's a bit like in C# if you had for (int i = 0; i <= array.Length; i++). In F# you can also declare loops like this: for i = 0 to endRange - 1 do.
for letter in [0 .. endRange] will go out of range again: It's going from 0 to endrange, which is the length of args. But args is getting shortened in the for loop, so it will eventually try to get a character from args that's out of range.
Now, the problem with the if..then..else statements, which is what I think you were looking at from the beginning.
if args.[letter] = '(' then
ParseStart <- letter
else if args.[letter] = ')' then
args <- args.Replace(args.[ParseStart .. letter], ComparisonSolver args.[ParseStart .. letter])
BoolParser args
Let's take the code within the two branches as two separate functions.
The first does ParseStart <- letter, which assigns letter to ParseStart. This function returns unit, which is F# equivalent of void.
The second does:
args <- args.Replace(args.[ParseStart .. letter], ComparisonSolver args.[ParseStart .. letter])
BoolParser args
This function returns a bool.
Now when you put them together in an if..then..else statement you have in one branch that results a unit and in the other in a bool. In this case it doesn't know which one to return, so it shows an "expression was expected to have type" error.
I strongly suspect that you wanted to call BoolParser args from outside
the for/if loop. But it's been indented so that F# treats it as part of the else if statement.
There are many ways to parse a boolean expression. It might be a good idea to look at the excellent library FParsec.
http://www.quanttec.com/fparsec/
Another way to implement parsers in F# is to use Active Patterns which can make for readable code
https://learn.microsoft.com/en-us/dotnet/fsharp/language-reference/active-patterns
It's hard to provide good error reporting through Active Patterns but perhaps you can find some inpiration from the following example:
let next s i = struct (s, i) |> Some
// Skips whitespace characters
let (|SkipWhitespace|_|) struct (s, i) =
let rec loop j =
if j < String.length s && s.[j] = ' ' then
loop (j + 1)
else
next s j
loop i
// Matches a specific character: ch
let (|Char|_|) ch struct (s, i) =
if i < String.length s && s.[i] = ch then
next s (i + 1)
else
None
// Matches a specific character: ch
// and skips trailing whitespaces
let (|Token|_|) ch =
function
| Char ch (SkipWhitespace ps) -> Some ps
| _ -> None
// Parses the boolean expressions
let parse s =
let rec term =
function
| Token 'T' ps -> Some (true, ps)
| Token 'F' ps -> Some (false, ps)
| Token '(' (Parse (v, Token ')' ps)) -> Some (v, ps)
| _ -> None
and opReducer p ch reducer =
let (|P|_|) ps = p ps
let rec loop l =
function
| Token ch (P (r, ps)) -> loop (reducer l r) ps
| Token ch _ -> None
| ps -> Some (l, ps)
function
| P (l, ps) -> loop l ps
| _ -> None
and andExpression ps = opReducer term '&' (&&) ps
and orExpression ps = opReducer andExpression '|' (||) ps
and parse ps = orExpression ps
and (|Parse|_|) ps = parse ps
match (struct (s, 0)) with
| SkipWhitespace (Parse (v, _)) -> Some v
| _ -> None
module Tests =
// FsCheck allows us to get better confidence in that the parser actually works
open FsCheck
type Whitespace =
| Space
type Ws = Ws of (Whitespace [])*(Whitespace [])
type Expression =
| Term of Ws*bool
| And of Expression*Ws*Expression
| Or of Expression*Ws*Expression
override x.ToString () =
let orPrio = 1
let andPrio = 2
let sb = System.Text.StringBuilder 16
let ch c = sb.Append (c : char) |> ignore
let token (Ws (l, r)) c =
sb.Append (' ', l.Length) |> ignore
sb.Append (c : char) |> ignore
sb.Append (' ', r.Length) |> ignore
let enclose p1 p2 f =
if p1 > p2 then ch '('; f (); ch ')'
else f ()
let rec loop prio =
function
| Term (ws, v) -> token ws (if v then 'T' else 'F')
| And (l, ws, r) -> enclose prio andPrio <| fun () -> loop andPrio l; token ws '&' ;loop andPrio r
| Or (l, ws, r) -> enclose prio orPrio <| fun () -> loop orPrio l ; token ws '|' ;loop orPrio r
loop andPrio x
sb.ToString ()
member x.ToBool () =
let rec loop =
function
| Term (_, v) -> v
| And (l, _, r) -> loop l && loop r
| Or (l, _, r) -> loop l || loop r
loop x
type Properties() =
static member ``Parsing expression shall succeed`` (expr : Expression) =
let expected = expr.ToBool () |> Some
let str = expr.ToString ()
let actual = str |> parse
expected = actual
let fscheck () =
let config = { Config.Quick with MaxTest = 1000; MaxRejected = 1000 }
Check.All<Properties> config
I've got a discriminated union tree like this:
type rbtree =
| LeafB of int
| LeafR of int
| Node of int*rbtree*rbtree
And what I have to do is to search for every LeafB present in the tree, so I came with a this recursive function:
let rec searchB (tree:rbtree) : rbtree list =
match tree with
| LeafB(n) -> LeafB(n)::searchB tree
| LeafR(n) -> []
| Node(n,left,right) -> List.append (searchB left) (searchB right)
But when I try to test it I get stack overflow exception and I have no idea how to modify it to work properly.
As #kvb says your updated version isn't truely tail-rec and might cause a stackoverflow as well.
What you can do is using continuations essentially using heap space instead of stack space.
let searchB_ tree =
let rec tail results continuation tree =
match tree with
| LeafB v -> continuation (v::results)
| LeafR _ -> continuation results
| Node (_, lt, rt) -> tail results (fun leftResults -> tail leftResults continuation rt) lt
tail [] id tree |> List.rev
If we looks at the generated code in ILSpy it looks essentially like this:
internal static a tail#13<a>(FSharpList<int> results, FSharpFunc<FSharpList<int>, a> continuation, Program.rbtree tree)
{
while (true)
{
Program.rbtree rbtree = tree;
if (rbtree is Program.rbtree.LeafR)
{
goto IL_34;
}
if (!(rbtree is Program.rbtree.Node))
{
break;
}
Program.rbtree.Node node = (Program.rbtree.Node)tree;
Program.rbtree rt = node.item3;
FSharpList<int> arg_5E_0 = results;
FSharpFunc<FSharpList<int>, a> arg_5C_0 = new Program<a>.tail#17-1(continuation, rt);
tree = node.item2;
continuation = arg_5C_0;
results = arg_5E_0;
}
Program.rbtree.LeafB leafB = (Program.rbtree.LeafB)tree;
int v = leafB.item;
return continuation.Invoke(FSharpList<int>.Cons(v, results));
IL_34:
return continuation.Invoke(results);
}
So as expected with tail recursive functions in F# it is tranformed into a while loop. If we look at the non-tail recursive function:
// Program
public static FSharpList<int> searchB(Program.rbtree tree)
{
if (tree is Program.rbtree.LeafR)
{
return FSharpList<int>.Empty;
}
if (!(tree is Program.rbtree.Node))
{
Program.rbtree.LeafB leafB = (Program.rbtree.LeafB)tree;
return FSharpList<int>.Cons(leafB.item, FSharpList<int>.Empty);
}
Program.rbtree.Node node = (Program.rbtree.Node)tree;
Program.rbtree right = node.item3;
Program.rbtree left = node.item2;
return Operators.op_Append<int>(Program.searchB(left), Program.searchB(right));
}
We see the recursive call at the end of the function Operators.op_Append<int>(Program.searchB(left), Program.searchB(right));
So the tail-recursive function allocates continuations functions instead of creating a new stack frame. We can still run out of heap but there's lot more heap than stack.
Full example demonstrating a stackoverflow:
type rbtree =
| LeafB of int
| LeafR of int
| Node of int*rbtree*rbtree
let rec searchB tree =
match tree with
| LeafB(n) -> n::[]
| LeafR(n) -> []
| Node(n,left,right) -> List.append (searchB left) (searchB right)
let searchB_ tree =
let rec tail results continuation tree =
match tree with
| LeafB v -> continuation (v::results)
| LeafR _ -> continuation results
| Node (_, lt, rt) -> tail results (fun leftResults -> tail leftResults continuation rt) lt
tail [] id tree |> List.rev
let rec genTree n =
let rec loop i t =
if i > 0 then
loop (i - 1) (Node (i, t, LeafB i))
else
t
loop n (LeafB n)
[<EntryPoint>]
let main argv =
printfn "generate left leaning tree..."
let tree = genTree 100000
printfn "tail rec"
let s = searchB_ tree
printfn "rec"
let f = searchB tree
printfn "Is equal? %A" (f = s)
0
Oh, I might came with an solution:
let rec searchB (tree:rbtree) : rbtree list =
match tree with
| LeafB(n) -> LeafB(n)::[]
| LeafR(n) -> []
| Node(n,left,right) -> List.append (searchB left) (searchB right)
Now it looks like working properly when I try it.
This question already has answers here:
Why doesn't pattern matching on a property of a record compile?
(2 answers)
Closed 6 years ago.
Why isn't pattern matching on an assigned value recognized
I receive a warning when I attempt to pattern match on the value called target:
[<Test>]
let ``set center cell to alive``() =
// Setup
let target = (2,2)
let grid = createGrid 9 |> Map.map (fun k v ->
match k with
| target -> { v with Status=Alive }
| _ -> v)
// Test
let center = grid |> getStatus (2,2)
// Verify
center |> should equal Alive
The warning points to:
| target -> { v with Status=Alive }
| _ -> v)
Specifically on:
| _ -> v)
The warning is:
This rule will never be reached.
Which forces me to not use target and instead hard-code the value in order to resolve the warning:
[<Test>]
let ``set center cell to alive``() =
// Setup
let grid = createGrid 9 |> Map.map (fun k v ->
match k with
| (2,2) -> { v with Status=Alive }
| _ -> v)
// Test
let center = grid |> getStatus (2,2)
// Verify
center |> should equal Alive
Can someone explain why I can't do this?
Full code:
type Status = Alive | Dead
type Cell = { X:int; Y:int; Status:Status }
let isNeighbor cell1 cell2 =
let isAbsNeighbor v1 v2 =
match abs (v1 - v2) with
| 0 | 1 -> true
| _ -> false
let isValueNeighbor v1 v2 =
match v1 >= 0
&& v2 >= 0 with
| true -> isAbsNeighbor v1 v2
| _ -> isAbsNeighbor v2 v1
match cell1.X <> cell2.X
|| cell1.Y <> cell2.Y with
| true -> isValueNeighbor cell1.X cell2.X
&& isValueNeighbor cell1.Y cell2.Y
| _ -> false
let createGrid rowCount =
[for x in 1..rowCount do
for y in 1..rowCount do
yield { X=x; Y=y; Status=Dead } ]
|> List.map (fun c -> (c.X, c.Y), { X=c.X; Y=c.Y; Status=Dead })
|> Map.ofList
let getStatus coordinate (grid:Map<(int * int), Cell>) =
match grid.TryFind coordinate with
| Some cell -> cell.Status
| None -> Dead
In a match expression, the rule
match k with
| target -> { v with Status=Alive }
unconditionally matches and binds k to a name target which shadows the existing definition. This means the following clause will never be reached. You can use a conditional match:
match k with
| t when t = target -> { v with Status = Alive }
| _ -> v
According to Pattern Matching your target is a variable pattern, so it shadows the original target value.
Pattern matching is useful for destructuring the matched object, for a simple test plain if-else is preferrable (in my opinion).
A use-case for pattern matching would be if you want to test several cases. Instead of a when guard you could then also use active patterns:
let (|Eq|_|) expected actual =
if expected = actual then Some()
else None
let target = (2,2)
let attempt = (3,2)
match attempt with
| Eq target -> Some "Bulls eye"
| (2, _) -> Some "Almost"
| t when fst t > 20 -> Some "Quite the contrary"
| _ -> None
I need to check if a list starts with another, shorter list. The function, when using when guards is trivial:
let rec startsWith l1 l2 =
match l1, l2 with
| [], _ | _, [] -> true
| x::xs, y::ys when x = y -> startsWith xs ys
| _ -> false
let lst1 = [ 1; 2; 1 ]
let lst2 = [ 1; 2; 1; 2; 3; ]
let lst3 = [ 1; 3; 1; 2; 3; ]
let c1 = startsWith lst1 lst2 // true
let c2 = startsWith lst1 lst3 // false
But whatever I tried along the lines of an Active Pattern:
let (|HeadsMatch|) (l1 : ('a) list) (l2 : ('a) list) =
if l1.Head = l2.Head then Some(l1.Tail, l2.Tail) else None
let rec startsWith l1 l2 =
match l1, l2 with
| [], _ | _, [] -> true
| HeadsMatch /* need to capture the result */ -> startsWith t1 t2
| _ -> false
I could not make to compile. How to make a version of this function using Active pattern? And if this is not possible, can you explain why?
P.S. Any other nice ways to write the above function?
EDIT: I took the snippet from Daniel's answer to not distract from the real question.
EDIT: My problems started in the beginning. I have defined the active pattern function as
let (|HeadsMatch|_|) lst1 lst2 =
but it should have been
let (|HeadsMatch|_|) (lst1, lst2) =
In which case it would match as in the accepted answer.
I suppose the nicest way might be
let startsWith = Seq.forall2 (=)
If you want to write it from scratch, you need to match on both lists:
let rec startsWith l1 l2 =
match l1, l2 with
| [], _ | _, [] -> true
| x::xs, y::ys when x = y -> startsWith xs ys
| _ -> false
If you want to write it with an active pattern for learning purposes, using Tarmil's definition it would be
let rec startsWith l1 l2 =
match l1, l2 with
| [], _ | _, [] -> true
| HeadsMatch(xs, ys) -> startsWith xs ys
| _ -> false
There are two errors in the definition of your active pattern:
It's a partial active pattern (since it is possible that it doesn't match) so the syntax is (|HeadsMatch|_|).
You need to take the two lists as a pair, since you want to match lst1, lst2.
With these, the code will compile. But it will throw an exception at runtime, because you use .Head and .Tail on lists when you don't know if they have any element; you're not defining the behavior if one of the lists is empty.
Here is an idiomatic implementation of HeadsMatch:
let (|HeadsMatch|_|) (lst1, lst2) =
match (lst1, lst2) with
| (x :: xs, y :: ys) when x = y -> Some (xs, ys)
| _ -> None
Without guard:
let (|HeadsMatch|_|) (lst1, lst2) =
match (lst1, lst2) with
| (x :: xs, y :: ys) ->
if x = y then Some (xs, ys) else None
| _ -> None
As a side-note, your first implementation has the same problem. The following will throw a runtime exception:
startsWith [1;2] [1]
because you don't check that lst2 is not empty before using .Head and .Tail on it. In general, you should avoid these two methods almost all the time.