Allow further expressions as active pattern arguments
Allow arbitrary expressions as active pattern arguments
I propose we allow:
- Property access Already allowed:
let (|Eq|_|) x = (=) x >> function true -> Some Eq | false -> None
let l = "123".Length
match 3 with
| Eq l -> printfn "A"
| _ -> printfn "B"
Should allow:
let (|Eq|_|) x = (=) x >> function true -> Some Eq | false -> None
match 3 with
| Eq "123".Length -> printfn "A"
(*
error FS0010: Unexpected symbol '.' in pattern matching. Expected '->' or other token.
error FS0010: Unexpected symbol '.' in pattern matching. Expected '->' or other token.
error FS0001: This expression was expected to have type
'int'
but here has type
'string'
*)
| _ -> printfn "B"
-
typeof(https://github.com/fsharp/fslang-suggestions/issues/758) Already allowed:
let (|Eq|_|) x = (=) x >> function true -> Some Eq | false -> None
let t = typeof<int>
match typeof<int> with
| Eq t -> printfn "A"
| _ -> printfn "B"
Should allow:
let (|Eq|_|) x = (=) x >> function true -> Some Eq | false -> None
match typeof<int> with
| Eq typeof<int> -> printfn "A" // error FS0010: Unexpected type application in pattern matching. Expected '->' or other token.
// error FS0010: Unexpected type application in pattern matching. Expected '->' or other token.
// error FS0685: The generic function 'typeof' must be given explicit type argument(s)
| _ -> printfn "B"
- Lambdas (
funandfunction) Currently allowed:
let (|Member|) f x = f x
let f (x:System.Type) = x.BaseType
match typeof<int> with
| Member f (Member f null) -> printfn "A"
| _ -> printfn "B"
Should allow:
let (|Member|) f x = f x
match typeof<int> with
| Member (fun x -> x.BaseType) (Member (fun x -> x.BaseType) null) -> printfn "A"
(*
error FS0010: Unexpected keyword 'fun' in pattern. Expected ')' or other token.
error FS0583: Unmatched '('
error FS0010: Unexpected symbol ')' in implementation file
error FS0010: Unexpected keyword 'fun' in pattern. Expected ')' or other token.
error FS0583: Unmatched '('
error FS0010: Unexpected symbol ')' in implementation file
error FS0001: Type mismatch. Expecting a
'System.Type -> 'a'
but given a
'('b -> 'c) -> 'b -> 'c'
The type 'System.Type' does not match the type ''a -> 'b'
*)
| _ -> printfn "B"
(Imagine this with #506 :wink:)
let (|Member|) f x = f x
match typeof<int> with
| Member _.BaseType (Member _.BaseType null) -> printfn "A"
| _ -> printfn "B"
(Partially applied operators are already ok too!)
let (|Apply|) f x = f x
match 1 with
| Apply ((+) 1) (Apply ((+) 1) 3) -> printfn "A"
| _ -> printfn "B"
- Computation expressions Currently allowed:
let (|SeqEqual|_|) a = Seq.map2 (=) a >> Seq.fold (&&) true >> function true -> Some SeqEqual | false -> None
let otherSeq = seq { 1; 2; 3 }
match seq { 1; 2; 3 } with
| SeqEqual otherSeq -> printfn "A"
| _ -> printfn "B"
Should allow:
let (|SeqEqual|_|) a = Seq.map2 (=) a >> Seq.fold (&&) true >> function true -> Some SeqEqual | false -> None
match seq { 1; 2; 3 } with
| SeqEqual (seq { 1; 2; 3 }) -> printfn "A"
(*
error FS0010: Unexpected integer literal in pattern. Expected identifier, 'global' or other token.
error FS0583: Unmatched '('
error FS0010: Unexpected integer literal in pattern. Expected identifier, 'global' or other token.
error FS0583: Unmatched '('
error FS0723: Invalid argument to parameterized pattern label
*)
| _ -> printfn "B"
- Records Already allowed:
let (|Eq|_|) x = (=) x >> function true -> Some Eq | false -> None
type X = { x : int }
let x = { x = 3 }
match { x = 3 } with
| Eq x -> printfn "A"
| _ -> printfn "B"
Should allow:
let (|Eq|_|) x = (=) x >> function true -> Some Eq | false -> None
type X = { x : int }
match { x = 3 } with
| Eq { x = 3 } -> printfn "A"
// error FS0723: Invalid argument to parameterized pattern label
| _ -> printfn "B"
- Anonymous records Already allowed:
let (|Eq|_|) x = (=) x >> function true -> Some Eq | false -> None
let x = {| x = 3 |}
match {| x = 3 |} with
| Eq x -> printfn "A"
| _ -> printfn "B"
Should allow:
let (|Eq|_|) x = (=) x >> function true -> Some Eq | false -> None
match {| x = 3 |} with
| Eq {| x = 3 |} -> printfn "A"
// error FS0010: Unexpected symbol '{|' in pattern
// error FS0010: Unexpected symbol '{|' in pattern
| _ -> printfn "B"
The existing way of approaching this problem in F# is to annoyingly put the argument in a let binding elsewhere!
Pros and Cons
The advantages of making this adjustment to F# are
- Consistency with the rest of F#
- Convenience when using active patterns
- Concise matching with active patterns
The disadvantages of making this adjustment to F# are (none).
Extra information
Estimated cost (XS, S, M, L, XL, XXL): S
Related suggestions: (put links to related suggestions here)
https://github.com/fsharp/fslang-suggestions/issues/758 - This while only being scoped to typeof.
Affidavit (please submit!)
Please tick this by placing a cross in the box:
- [x] This is not a question (e.g. like one you might ask on stackoverflow) and I have searched stackoverflow for discussions of this issue
- [x] I have searched both open and closed suggestions on this site and believe this is not a duplicate
- [x] This is not something which has obviously "already been decided" in previous versions of F#. If you're questioning a fundamental design decision that has obviously already been taken (e.g. "Make F# untyped") then please don't submit it.
Please tick all that apply:
- [x] This is not a breaking change to the F# language design
- [x] I or my company would be willing to help implement and/or test this
For Readers
If you would like to see this issue implemented, please click the :+1: emoji on this issue. These counts are used to generally order the suggestions by engagement.
Also for context: https://github.com/dotnet/fsharp/issues/870 https://github.com/dotnet/fsharp/issues/4828 https://github.com/dotnet/fsharp/issues/5500
This is a missing case in
let rec convSynPatToSynExpr x =The F# language spec should really over which syntactic forms are converted.
But more importantly, F# pattern syntax doesn't actually have any syntax for type applications today, which is the core problem here. I think it is easy enough to add one but it would involve adding a case to the parser.
So Id be very happy to see this addressed but we should use a short RFC for it (consider it pre-approved)
Why not all expressions??
Why not all expressions??
At a high level it would be ideal to accept all expressions. However this would not be backwards compatible. The F# pattern syntax is a different part of the parser grammar to the expression syntax. However they have a convertible subset.
For example
-
1is both a pattern and an expression. -
1 & 1is a valid pattern but not a valid expression -
for x in 1 .. 2 do printfn "4"is a valid expression but not a valid pattern
Now, for better or worse the thing that is an active pattern argument is, syntactically, a pattern, which must lie in the subset that is convertible. That's what convSynPatToSynExpr does. So only convertible patterns are admitted.
@dsyme I don't think you can put patterns when expressions are expected (active pattern inputs), nor put expressions where patterns are expected (active pattern outputs).
@dsyme I don't think you can put patterns when expressions are expected (active pattern inputs)
At the parser level, this isn't the case - almost, but not quite. This is because we allow the pattern to be dropped for a unit return type, e.g.
For example:
let (|A|_|) (n: int) (x:int) = if n = x then Some() else None;;
let f x =
match x with
| A 3 -> 1
| A 4 -> 2
| _ -> 3
Here 3 and 4 look like pattern syntax but end up as active pattern arguments (expressions). This is the origin of convSynPatToSynExpr
Note I'm 100% ok with augmenting the pattern syntax to cover as many of the cases you mention above as possible, subject to backwards compatibility constraints.
Are pattern syntaxes and expression syntaxes mutually exclusive? Can't a union of syntaxes be parsed and then error later?
Are pattern syntaxes and expression syntaxes mutually exclusive?
Yes, there are enough small differences that's not possible.
In retrospect I may have unified these syntaxes originally. However there are also good reasons not to do that, as this is the only corner-case point where this matters, and it may have prevented other work
For reference: https://github.com/dotnet/fsharp/blob/a70f3beacfe46bcd653cc6d525bd79497e6dd58e/src/fsharp/pars.fsy#L3238-L3241
/* Also, it is unexpected that '(a, b : t)' in a pattern binds differently to */
/* '(a, b : t)' in an expression. It's not that easy to solve that without */
/* duplicating the entire expression grammar, or making a fairly severe breaking change */
/* to the language. */
What about interpolated strings?
let (|Eq|_|) comp value =
if comp = value then Some() else None
let number = 22
let str = "Catch-22"
match str with
| Eq $"Catch-%d{number}" -> // error FS0010: Unexpected interpolated string (first part) in pattern
printfn "Same"
| _ ->
printfn "Not same"
What about interpolated strings?
Yes, they should be added to the available expressions.
Marking as approved-in-principle because there are definite cases that should be allowed, as described above.
Matching patterns of interpolated string and use the matched pattern's interpolation for stuffs can be a really easy way to write parser for things :
match string with
| ($"<div>{innerHtml}</div>") ->
printfn (innerHtml)
| ($"<img src = "{url}"/>") ->
printfn (url)
| _ -> ()
let rec parser string depth number_of_div =
match string with
| ($"<div>{innerHtml}</div>") ->
parser innerHtml (depth + 1) (number_of_div + 1)
| ($"<{_}>{innerHtml}</{_}>") ->
parser innerHtml (depth + 1) (number_of_div)
| _ -> struct {|depth = depth; div_count = number_of_div|}
If logics behind can process this pattern really efficiently this would be powerful I would be feeling so good about writing codes and building things and the satisfaction is incomparable for me coding using C#