Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multiple pattern cases with 'when' clauses #715

Open
Happypig375 opened this Issue Jan 20, 2019 · 4 comments

Comments

Projects
None yet
5 participants
@Happypig375
Copy link

Happypig375 commented Jan 20, 2019

Multiple pattern cases with 'when' clauses

I propose we allow multiple patterns with 'when' to be or'ed together.

The existing way of approaching this problem in F# is using an if-statement or even duplicated code. 馃槩

Consider the following code:

type internal 'T memory = System.Memory<'T>
type internal chars = char memory

[<Struct>]
type UnicodeCharacter = private UnicodeCharacter of chars
module UnicodeCharacter =
    let Create (x:chars) =
        match x.Length with
        | 1 when x.Span.[0] |> System.Char.IsSurrogate |> not
        | 2 when x.Span.[0] |> System.Char.IsHighSurrogate && x.Span.[1] |> System.Char.IsLowSurrogate ->
            x |> UnicodeCharacter |> ValueSome
        | _ -> ValueNone
    let (|UnicodeCharacter|) (UnicodeCharacter x) = x

Currently the F# compiler errs out with the message

FS0010	Unexpected symbol '|' in pattern matching. Expected '->' or other token.

At the | in | 2 when x.Span.[0].

I propose that we allow this instead of having to duplicate the case handling code, with a slight syntax change because match 1 with | 2 | 1 when false -> true | _ -> false is false, implying that when applies to the whole 2 | 1:

        match x.Length with
        | 1 when x.Span.[0] |> System.Char.IsSurrogate |> not ->
        | 2 when x.Span.[0] |> System.Char.IsHighSurrogate && x.Span.[1] |> System.Char.IsLowSurrogate ->
            x |> UnicodeCharacter |> ValueSome
        | _ -> ValueNone

If | is after ->, then the two patterns are combined.

Pros and Cons

The advantages of making this adjustment to F# are more conciseness, less typing, less cognitive load!

The disadvantage of making this adjustment to F# is that as with any syntax change, we might be making F# more complex.

Extra information

Estimated cost (XS, S, M, L, XL, XXL): S

Related suggestions: Couldn't find any, please point it if any

Affidavit (please submit!)

Please tick this by placing a cross in the box:

  • This is not a question (e.g. like one you might ask on stackoverflow) and I have searched stackoverflow for discussions of this issue
  • I have searched both open and closed suggestions on this site and believe this is not a duplicate
  • 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:

  • This is not a breaking change to the F# language design
  • I or my company would be willing to help implement and/or test this

@Happypig375 Happypig375 changed the title Or patterns with 'when' clauses Multiple pattern cases with 'when' clauses Jan 20, 2019

@jindraivanek

This comment has been minimized.

Copy link

jindraivanek commented Jan 21, 2019

Current state

when guard can be used only for whole "subpattern" (before ->). For details see:

https://fsharp.org/specs/language-spec/4.1/FSharpSpec-4.1-latest.pdf#page=115&zoom=auto,-82,755

Workaround

We can create When active pattern:

let (|When|_|) p _ = if p then Some () else None

Your example can be then written as:

match x.Length with
    | 1 & When (x.Span.[0] |> System.Char.IsSurrogate |> not)
    | 2 & When (x.Span.[0] |> System.Char.IsHighSurrogate && x.Span.[1] |> System.Char.IsLowSurrogate) ->
        x |> UnicodeCharacter |> ValueSome
    | _ -> ValueNone
@theprash

This comment has been minimized.

Copy link

theprash commented Jan 21, 2019

@jindraivanek Unfortunately that doesn't actually work because for some reason active pattern inputs can't be arbitrary expressions. For instance they don't seem to support infix operators. With When (1 = 1) you get error FS0010: Unexpected symbol '=' in pattern. Expected ')' or other token., but When ((=) 1 1) compiles. Perhaps allowing any expression would be a good language suggestion.

@jindraivanek

This comment has been minimized.

Copy link

jindraivanek commented Jan 21, 2019

@theprash Thanks for pointing that out, wasn't aware that infix operators didn't work in patterns. Looks like also other things don't work, like lambdas or indexing. Basically only function calls and other patterns are allowed. I don't see why any expression shoudln't be allowed, because you can do it by calling custom function. Agree it would be good addition.

So, my workaround is not helping much here.

@glchapman

This comment has been minimized.

Copy link

glchapman commented Mar 4, 2019

Another possibility might be to extend array pattern syntax to System.Memory. If that were possible, you could do something like;

let |Surrogate|_| c = if System.Char.IsSurrogate c then Some () else None
// etc for other Char tests -- would be nice to be able to use ValueOption here

let Create (x: chars) =
    match x with
    | [| Surrogate |] -> ValueNone
    | [| _ |]
    | [| HighSurrogate; LowSurrogate |] ->
        ValueSome (UnicodeCharacter x)
    | _ -> ValueNone

Edit: Actually, I guess if the array pattern syntax is extended, it should be extended to Span, with support for implicitly casting arrays to Span when used in pattern matching.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can鈥檛 perform that action at this time.