fsharp / fsharp/fslang-suggestions
Object/property/field patterns
- Dominant language
- No language data
- Stars
- 373
- Forks
- 21
- PR merge metrics
- No merged PRs in 30d
Description
Trimmed down suggestion written by @dsyme: I propose we allow two additions - property/field patterns, and "object patterns".
### Proposed syntax for property/field patterns:
```fsharp
match x with
| _.Property1 pattern -> ....
```
The pattern may be parenthesized, e.g.
```fsharp
match x with
| _.Elements([a;b;c]) -> ....
```
Property patterns can use nesting, so this is allowed:
```fsharp
match x with
| _.Property1.Property2 pat -> ...
```
Property patterns can resolve to fields - as supported in #506
Boolean property patterns may elide a `true` pattern. (Will consider whether this also applies to other pattern elements)
```fsharp
match x with
| _.IsCold -> ...
```
Notes:
1. Property patterns depend on #506
2. Property patterns can not resolve to methods. Use an active pattern, it's what they're there for.
3. Property patterns can't resolve to indexers. Just use an active pattern, it's what they're there for. So not this:
```fsharp
match x with
| _.[3] pat -> ...
```
### Proposed syntax for object patterns:
```fsharp
match x with
| (Property1=pattern, Property2=pattern, Field3=pattern) -> ....
```
The type name can be given explicitly (if it doesn't already exist as a pattern discriminator):
```fsharp
match x with
| SomeObjectType(Property1=pattern, Property2=pattern, Field3=pattern) -> ....
```
Existing type-test patterns would be extended to allow object patterns:
```fsharp
let f3 (inp: obj) =
match inp with
| :? SubType1(X=3) -> 1
| :? SubType2(X=3, Y=4)-> 1
| _ -> 2
```
Existing type-test patterns would also be extended to non-object patterns such as unions and records:
```fsharp
type U = U of int * int
type R = { A: int; B: int }
let f3 (inp: obj) =
match inp with
| :? SubType1(X=3) -> 1
| :? { A = 1; B = 2 }-> 2
| :? U(a,b) -> 3
| _ -> 4
```
Notes
1. Object patterns can't use nesting of property names, so not
```fsharp
match x with
| (Property1.Property2=pat, Property3=pat) -> ...
```
This is because the corresponding object creation syntax doesn't support nesting
2. Object patterns can't use indexers. This is because the corresponding object creation syntax doesn't support nesting
3. Object patterns can be used on records, despite the lack of a corresponding syntax for record construction
```fsharp
type R = { X: int; Y: int }
match x with
| (X=pat, Y=pat) -> ...
```
4. Where the above don't fit, use an active pattern. It's what they're there for.
Discussion and further suggestions below
------
Original suggestion:
# Generalized collection patterns
Currently, list and array patterns can only match based on length, or in list's case, unconsing the first element and the rest of the list. We don't have patterns to match based on starting and ending elements, or patterns to match arbitrary types with indexes.
I propose we allow
1. Slice patterns
`function [| firstElem; _; thridElem; ..; secondToLastElem; _|] -> f firstElem thridElem secondToLastElem |> Some | _ -> None`
The two dots indicate skipping zero or more elements. We can use `as` to get the sliced area: `let unsnoc = function [.. as s; lastElem] -> s, lastElem` Only one slice is allowed per collection for now.
2. Seq patterns
`function seq { _; secondElem; .. } -> Some secondElem | _ -> None`
We should be able to match on arbitrary sequences. If they are of type `IReadOnlyList`, `IList` or `IList`, we can even access by index. If there are multiple matches, we should cache the elements to a ResizeArray and access by index. The empty case should be `seq []`, to unify with how we construct seqs.
Sometimes we just want to match by index. Moreover, these collections may be hidden inside properties and fields.
The existing way of approaching this problem in F# is performing length checks and accessing indexes, or in the special cases, using library `head` and `last` functions.
## Pros and Cons
The advantages of making this adjustment to F# are
1. More uses for collection patterns
2. Less code to write
3. Being able to apply patterns to even more types
The disadvantage of making this adjustment to F# is the overlap with active patterns. However, active patterns completely disable completeness checks so an unnecessary catch-all pattern must be used every time.
## Extra information
Estimated cost (XS, S, M, L, XL, XXL): M to L
Related suggestions:
[Champion "list pattern" for C#](https://github.com/dotnet/csharplang/issues/3435)
[List patterns proposal for C#](https://github.com/dotnet/csharplang/blob/5268b51794041c31eab5173a66fa04fa2b91325b/proposals/list-patterns.md)
[C# 8 recursive pattern matching](https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-8.0/patterns)
[F# pattern matching: parity with C# 9](https://github.com/fsharp/fslang-suggestions/issues/793)
## 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](http://stackoverflow.com)) and I have searched stackoverflow for discussions of this issue
* [x] I have [searched both open and closed suggestions on this site](http://github.com/fsharp/fslang-suggestions/issues) 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.
Contributor guide
No contributing guide indexed for this repository
Assessment
This issue has not been assessed yet.