fsharp / fslang-suggestions

The place to make suggestions, discuss and vote on F# language and core library features
344 stars 21 forks source link

if let expression #705

Closed vasily-kirichenko closed 9 months ago

vasily-kirichenko commented 6 years ago

https://github.com/MangelMaxime/Fulma/blob/1cbbc35e046007029e4b382d502cd9b301443a69/src/Fulma/Elements/Form/Textarea.fs#L144-L151

if Option.isSome opts.Id then yield Props.Id opts.Id.Value :> IHTMLProp
if Option.isSome opts.Value then yield Props.Value opts.Value.Value :> IHTMLProp
if Option.isSome opts.DefaultValue then yield Props.DefaultValue opts.DefaultValue.Value :> IHTMLProp
if Option.isSome opts.ValueOrDefault then
    yield Props.Ref <| (fun e -> if e |> isNull |> not && !!e?value <> !!opts.ValueOrDefault.Value then e?value <- !!opts.ValueOrDefault.Value) :> IHTMLProp
if Option.isSome opts.Placeholder then yield Props.Placeholder opts.Placeholder.Value :> IHTMLProp
if Option.isSome opts.OnChange then yield DOMAttr.OnChange opts.OnChange.Value :> IHTMLProp
if Option.isSome opts.Ref then yield Prop.Ref opts.Ref.Value :> IHTMLProp

I propose we add if let <pattern> then <expression> expression, so the above code would be rewritten as following:

if let (Some id) = opts.Id then yield Props.Id id :> IHTMLProp
if let (Some value) = opts.Value then yield Props.Value value :> IHTMLProp
if let (Some defValue) = opts.DefaultValue then yield Props.DefaultValue defValue :> IHTMLProp
if let (Some value) = opts.ValueOrDefault then
    yield Props.Ref <| (fun e -> if e |> isNull |> not && !!e?value <> !!value then e?value <- !!value) :> IHTMLProp
if let (Some p) = opts.Placeholder then yield Props.Placeholder p :> IHTMLProp
if let (Some onChange) = opts.OnChange then yield DOMAttr.OnChange onChange :> IHTMLProp
if let (Some r) = opts.Ref then yield Prop.Ref r :> IHTMLProp

It's inspired by the same Rust's feature, see https://doc.rust-lang.org/book/second-edition/ch06-03-if-let.html, and the same Swift's feature, see https://medium.com/@abhimuralidharan/if-let-if-var-guard-let-and-defer-statements-in-swift-4f87fe857eb6

The existing way of approaching this problem in F# is (see the first snippet, or match ... with Some x -> ... | None -> (), which is even more verbose, but safer).

Pros and Cons

The advantages of making this adjustment to F# are:

The disadvantages of making this adjustment to F# are:

Extra information

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

Affidavit (please submit!)

Please tick this by placing a cross in the box:

Please tick all that apply:

Happypig375 commented 5 years ago

I'm thinking of match?. match? x with Some x -> x // no need for None, implicitly returns ()

charlesroddie commented 5 years ago

match? is a good shorthand, because what we want is a slight variant on match.

match? x with pattern -> action // | pattern2 -> action2 ...
// equivalent to:
match x with pattern -> action | _ -> () // | pattern2 -> action2 ...

This is pattern matching, including both routing and binding of variables, so a very similar syntax makes sense.

if let is not the right syntax.

if let pattern = x then action // elif let pattern2 = x then action2 ...
  1. This takes two forms (let pattern = x in action and if x = expr then action) and merges them together without regard for the grammar of the originals. if is something very simple normally, taking a boolean condition, but nothing that begins with "let" here corresponds to a boolean.
  2. Users who are coding too quickly may get the order of pattern and x mixed up.
  3. The natural extension elif is too flexible, requiring = x to be typed in repeatedly.
7sharp9 commented 5 years ago

If let makes sense to me if the expression evaluated to Some or true. Match? I would expect the expression to also be an option. Swift has these with also guard for the opposite pattern so you can choose the most logical syntax.

On Sun, 13 Oct 2019 at 15:39, Charles Roddie notifications@github.com wrote:

match? is a good shorthand, because what we want here is a slight variant on match.

match x with pattern -> action | _ -> () // | pattern2 -> action2 ...match? x with pattern -> action // | pattern2 -> action2 ...

This is pattern matching, including both routing and binding of variables, so a very similar syntax makes sense. if let is not the right syntax.

if let pattern = x then action // elif let pattern2 = x then action2 ...

  1. Really this just takes two forms (let pattern = x in action and if x = expr then action) and merges them together without regard for the grammar of the originals. if is something very simple normally, taking a boolean condition, but nothing that begins with "let" here corresponds to a boolean.
  2. Users who are coding too quickly may get the order of pattern and x mixed up.
  3. The natural extension elif is too flexible, requiring = x to be typed in repeatedly.

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/fsharp/fslang-suggestions/issues/705?email_source=notifications&email_token=AAEPXSWWQHCDMMLQOCUIUHDQOMXILA5CNFSM4GCI6LC2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEBCXQ5A#issuecomment-541423732, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAEPXSWHDNHO3RYDOFLUY3TQOMXILANCNFSM4GCI6LCQ .

Happypig375 commented 5 years ago

If the expression is limited to an option, then it would duplicate Option.iter.

abelbraaksma commented 5 years ago

match? certainly is an interesting idea, but if it's going to mean 'a match that automatically inserts catch all four missing matches', then the return type of this catch-all should be the same, which is problematic.

Unless it's going to be only allowed in yield or yield! expressions, but this greatly limits the applicability.

Unless it's only allowed in if expressions and a successful match means true, a non-match false.

Another way of dealing with this is to allow active patterns to return a specific instance of an interface, let's say IMatch. This could then yield a property that is used as return type, and using match or match? can use this active pattern without ->..., which then becomes implicit. Such approach could be generic enough to simplify the use case of the OP, and to apply to many other use cases.

let x = if (match? foo with MyMatch) then...

Here, MyMatch implements IMatch, which gets treated specially (details TBD).

(just trying to think out of the box, not sure this is feasible, but the discussion here suggests that some improvements to matching are warranted)

Happypig375 commented 5 years ago

I would like match? to be able to accept arbitrary patterns. let x = match? foo with MyMatch x -> x // like if without else, this will infer the type of x as unit

Happypig375 commented 5 years ago

The IMatch becomes too much like a regular old if. It isn't needed.

charlesroddie commented 5 years ago

@7sharp9 If let makes sense to me if the expression evaluated to Some or true. Match? I would expect the expression to also be an option.

Neither if let nor match? would restrict patterns to options. Options were just an initial motivating example.

@abelbraaksma then the return type of this catch-all should be the same

Yes it's unit (for both if let and match?).

@abelbraaksma let x = if (match? foo with MyMatch) then... else ...

This is a separate shorthand suggestion, where foo matches MyMatch is equivalent to match foo with MyMatch -> true | _ -> false. I think it should go in a separate suggestion (with a better example because your example reduces to let x = match foo with MyMatch -> ... | _ -> ...). It's slightly related to https://github.com/fsharp/fslang-suggestions/issues/239.

7sharp9 commented 5 years ago

Well ? Is synonymous with try, and try with option, so if that wasn’t the case then it would be confusing.

On Sun, 13 Oct 2019 at 16:26, Charles Roddie notifications@github.com wrote:

@7sharp9 https://github.com/7sharp9 If let makes sense to me if the expression evaluated to Some or true. Match? I would expect the expression to also be an option.

Neither if let nor match? would restrict patterns to options. Options were just an initial motivating example.

@abelbraaksma https://github.com/abelbraaksma then the return type of this catch-all should be the same

Yes it's unit.

@abelbraaksma https://github.com/abelbraaksma let x = if (match? foo with MyMatch) then...

This is a separate shorthand suggestion, where foo matches MyMatch is equivalent to match foo with MyMatch -> true | -> false. I think it should go in a separate suggestion - with a better example because your example reduces to let x = match foo with MyMatch -> ... | -> .... It's slightly related to #239 https://github.com/fsharp/fslang-suggestions/issues/239.

— You are receiving this because you were mentioned.

Reply to this email directly, view it on GitHub https://github.com/fsharp/fslang-suggestions/issues/705?email_source=notifications&email_token=AAEPXSVMS7Q4F6QTQTXQIXDQOM4Y3A5CNFSM4GCI6LC2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEBCYT7A#issuecomment-541428220, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAEPXSV65RWR4G7YTT2SWOLQOM4Y3ANCNFSM4GCI6LCQ .

dsyme commented 5 years ago

What about ifmatch e.g.

ifmatch opts.Id with Some id then yield Props.Id id
elif ...
elifmatch ...
else ....
abelbraaksma commented 5 years ago

What about ifmatch e.g.

Could work and is unambiguous, but doesn't that imply introducing a formerly non-reserved keyword as new keyword?

FunctionalFirst commented 5 years ago

What's wrong with

#nowarn "25"
match opts.Id with Some id -> yield Props.Id id :> IHTMLProp

At least it's clear that some warning (completeness checking, in this case) is being ignored.

charlesroddie commented 5 years ago

@FunctionalFirst "The effect of disabling a warning applies to the entire file" (source).

FunctionalFirst commented 5 years ago

@charlesroddie Hmm, I didn't expect that. I also figured on something like #restore "25" being available, but it doesn't seem to be.

abelbraaksma commented 5 years ago

Hmm, I didn't expect that. I also figured on something like #restore "25" being available, but it doesn't seem to be.

There's an open suggestion somewhere that intends to solve this. It's already approved in principle, I believe, it just needs an RFC and an implementation.

dsyme commented 5 years ago

Could work and is unambiguous, but doesn't that imply introducing a formerly non-reserved keyword as new keyword?

Yes, though likely we could somehow turn off the feature if the user has defined a value/function called ifmatch

resolritter commented 4 years ago

What's wrong with

#nowarn "25"
match opts.Id with Some id -> yield Props.Id id :> IHTMLProp

Do you really think requiring a preprocessor directive for a code pattern is a good idea? IMO their intended purpose is definitely not to allow code to be more ergonomic. Imagine a whole code base polluted with this directive everywhere.

The suggestions are confusing to follow because there's no definite statement of the functionality which should be achieved. Here's one attempt to explain my understanding structurally:

if let will bind identifiers to a matched value, but only if the pattern, in fact, matches. It solves the problem of not needing to "unsafely" rely on previous assertions, because doing so leads to error-prone code when accessing the value(s) which went through the assertion. For instance, in the following code:

if Option.isSome opts.Id then yield opts.Id.Value

the yield part assumes Value is present because Option.isSome has been evaluated to true; however, a programmer could make the mistake of writing

if Option.isNone opts.Id then yield opts.Id.Value

which isn't invalid code, but it's wrong.

By the description above, we're interested in defining a binding (singular) if the pattern (singular) matched. It's not relevant to wonder about what would be returned in the other "branches" (this is not match), by that description, because we're only interested in the bound values from this single pattern. In other words, if let does not produce an expression with the matched value; it's a true/false statement which introduces new bindings, as far as evaluation is concerned. The same way you can't do:

let foo = let bar

It would not be possible to do

let foo = if let (Some id) = opts.Id


This rephrasing is more in-line with https://github.com/fsharp/fslang-suggestions/issues/705#issuecomment-436901840. Lots of other different ideas have been brought up, but from my perspective they're tied to this notion of if let behaving like match. While that would be more "powerful", if it's inconsistent or can't be made to work due to caveats, then can the proposal proceed in a more restricted form? I personally think an implementation with the intentions mentioned above would be useful as it is.

abelbraaksma commented 4 years ago

I propose the original issue text be updated (@vasily-kirichenko), I agree that it's difficult to understand what this proposal entails. Even with your comment, @resolritter, I still had to go over relevant bits in the comments.

The key part here is the binding, just like in match syntax, to prevent if x then y fail. I'm not sure it should be a requirement that the result be unit, it feels to me too limiting. The way I understand it, you can now write:

let f x = 
    match x with
    | Ok ok -> ok
    | _ -> // raise or return default value

With if let, let's call it ifmatch per @dsyme's comment, we can rewrite this as follows:

let f x = ifmatch x with Ok ok then ok else ... (* raise or return default *)

In the left-hand bound syntax proposal from @vasily-kirichenko, this would instead be:

let f x = if let (Ok ok) = x then ok else ... (* raise or return default *)

Which is essentially the following using existing syntax, but without the default raising on non-match:

// gives warning
let f x = let (Ok ok) = x in ok

In the absence of else, just like normal if, the return type must be unit.

The advantage of this syntax is that you can bind a value in the match syntax. The disadvantage is that it gives yet another way to do the same thing.


As an aside, this essentially reminds me of Perl syntax, that allows you to write $x = foo() or die(), which executes or-branch if the first is false. Which in and off itself suggests yet another, simpler, alternative syntax:

// using `or` cancels the completeness checking and executes the or-branch on non-match
let f x = let (Ok ok) = x in ok or raise "failed" 

// same with the unit-example from the OP
let f x = let (Ok ok) = x in log (ok) or ()

Thinking of this a bit, it kinda feels more natural and more importantly, better self-documenting the intention of the code.

resolritter commented 4 years ago

@abelbraaksma I appreciate the intention, but I think you arrived at the same "misinterpretation" I pointed out as the main problem in this discussion; or, rather, this different interpretation you have could be seen as another implementation take for the feature, but one that, in my view, expands on the scope intended from the motivating examples in the OP.

I'll reiterate from the previous comment

In other words, if let does not produce an expression with the matched value; it's a true/false statement which introduces new bindings, as far as evaluation is concerned.

Followed by

The same way you can't do:

let foo = let bar

It would not be possible to do

let foo = if let (Some id) = opts.Id

The point is not to make this new feature behave like a lesser version of match. if let (or ifmatch, or what have you) will not be assignable.

If you scroll all the way up to the first examples, the usage pattern proposed by @vasily-kirichenko does not imply any sort of RHS construct; while that might the case for Rust and possibly other languages, since we already have match in F#, there's not a good reason to introduce a lesser version of it through this feature.

Choosing between the two goes like this: if you want a RHS value decided from a pattern, use match. On the other hand, if you want to use a binding which matched during the evaluation of an if expression, use if let. Instead of thinking of "if let" as a couple, think of it as a let which happened during an if; that'll hopefully make this more explicit.

Going by this interpretation, it would not be relevant to discuss return values, or elif, or else, or anything else in that vein. And would it be useful enough like this? The previous post motivates some usages where it would make the code less error-prone. If it were to be implemented according to this explanation, I believe it'll also work for the motivating code in the first few posts on this thread.

https://github.com/fsharp/fslang-suggestions/issues/705#issue-378244682 https://github.com/fsharp/fslang-suggestions/issues/705#issuecomment-436823250 https://github.com/fsharp/fslang-suggestions/issues/705#issuecomment-436901840

abelbraaksma commented 4 years ago

@resolritter, I understand you cannot write let foo = let bar, as the second let misses a statement. It should be let foo = let bar = 1 in bar + 1. Likewise, let foo = if let (Some id) = opts.Id should be let foo = if let (Some id) = opts.Id then id else 42, or something like that.

In other words, if let does not produce an expression with the matched value; it's a true/false statement which introduces new bindings, as far as evaluation is concerned.

and

The point is not to make this new feature behave like a lesser version of match. if let (or ifmatch, or what have you) will not be assignable.

But assignment doesn't exist in F# and is not what I proposed, and that if let introduces a scoped binding is exactly what I showed (at least I tried).

Then you link to posts that appear to follow my suggestion of syntax. In your post, I don't see a suggestion of syntax or other examples, only things that you say are not possible. Something tells me that you think there should be no =-sign in this proposal, though all comments have the =-sign except for https://github.com/fsharp/fslang-suggestions/issues/705#issuecomment-436823250 by @cartermp, but I think that was just an error in writing it up as he talks about Swift and the like, which also have the =-sign in the syntax.

In my write-up and n your summary, we both talk about a new binding. Like in Swift, this new binding should be in scope of the if-block. And like current existing syntax, the lh-side contains the pattern. The binding would not be in scope of the else-block. As @vasily-kirichenko suggests, this is most useful for types that have two cases, like Result or Option.

Since any existing if block can currently be used at the rh-side of a binding, I would choose to keep that, as opposed to limiting this proposal to always returning unit. That's just my take, seeing the discussion above it is clearly not the take of the OP. The jury is still out on this ;).

I.e., currently this is legal:

let x = if foo = 42 then y else z

And likewise, principle of least surprise, this should be legal if this proposal were accepted:

let x = if let (Some foo) = myOpt then foo else 42

The previous post motivates some usages where it would make the code less error-prone.

In that/your post there are no examples of if let, or at least I'm not seeing them, I'm confused to what you mean here. There is an example on a regular if that could go haywire if coders use direct accessors to optional values like myOpt.Value, which should virtually never be used, whether or not this syntax is going to be accepted.


So, to summarize, I still don't quite get what you're aiming at, in fact, it seems to me that you are saying the same as I do, just using different terminology, and somehow we misunderstand each other. ;) Perhaps you can give some non-amgibuous examples that illustrate what you mean and examples with the new syntax that illustrate what you consider illegal.

PS: I still would like to ask @vasily-kirichenko to rewrite the original proposal with examples that don't require domain knowledge, preferably as concise as possible. Plus a summary of the alternatives discussed in the thread. Considering the input from @dsyme and @cartermp, it feels like this syntax may get accepted, but it would be nice if we have a clear summary to continue discussion on.

resolritter commented 4 years ago

I have misread https://github.com/fsharp/fslang-suggestions/issues/705#issuecomment-699485229 in the sense that I thought it was disagreeing with the explanation.

What honestly prompted my participation in this thread was reading those sorts of comments

https://github.com/fsharp/fslang-suggestions/issues/705#issuecomment-437048389 https://github.com/fsharp/fslang-suggestions/issues/705#issuecomment-542385848 https://github.com/fsharp/fslang-suggestions/issues/705#issuecomment-436906276

where match's functionality is being compared to what's expected out of this new construct in its simplest form, if we are to consider the motivating examples. I argue that it's not a welcome comparison because match expressions produce a useful value, whereas in order to solve the needs raised in the OP, if let can be limited to a true/false evaluation which has the side-effect of defining bindings. In other words, if let being "limited" to this use-case would still be enough useful; perhaps more importantly, it would not overlap with match, while still filling a gap which can't be solved elegantly currently (closest I found was https://github.com/fsharp/fslang-suggestions/issues/705#issuecomment-542385848, but it requires a pragma and it's also clunkier).

I would choose to keep that, as opposed to limiting this proposal to always returning unit

In contrast to this, I am insisting on the premise that "limiting" the functionality sounds like a good idea. Having if let fit a very specific purpose has the upside of not introducing two ways of expressing the same thing, thus it would not be used anywhere match could be used, vice versa.

Finally,

rewrite the original proposal with examples that don't require domain knowledge, preferably as concise as possible

This would be a good move for progressing the proposal. Lots of suggestions have been raised already; if they can be broadly separated into two (or N) camps

  1. if let should behave somewhat like a variant of match
  2. if let should be a true or [false|unit|...] statement which binds from a single pattern

Each different proposition can be motivated with examples of where/how they would be used, and then hopefully inform a decision of what's worth pursuing. It might be the case that e.g. (1) has too many caveats and is infeasible, (2) is not useful enough, etc.

Swoorup commented 4 years ago

Compelling case for both if let and elif let: Not that I would encourage writing both styles, but helps with non-uniform expressions.

let someDumbFn () =
  let a = None
  let b = None
  let c = None
  let d = None
  let e = None
  let f = None
  let g = None
  let h = None
  let i = None
  let j = None
  let k = None
  let l = None

  match (a, b, c, d, e, f, g, h, i, j, k, l) with
  | (Some a, _, _, _, _, _, _, _, _, _, _, _) -> printfn "a: %A" a
  | (None, Some b, _, _, _, _, _, _, _, _, _, _) -> printfn "b: %A" b
  | (None, None, Some c, Some d, Some e, Some f, Some g, Some h, Some i, Some j, Some k, Some l) -> printfn "WAH..."
  | _ -> printfn "Bad luck son."

  if let (Some a) = a then
    printfn "a: %A" a
  elif let (Some b) = b then
    printfn "b: %A" b
  elif let (Some c) = c
    && let (Some d) = d
    && let (Some e) = e
    && let (Some f) = f
    && let (Some g) = g
    && let (Some h) = h
    && let (Some i) = i
    && let (Some j) = j
    && let (Some k) = k
    && let (Some l) = l then
    printfn "WAH..."
  else
    printfn "Bad luck son."
abelbraaksma commented 4 years ago

@Swoorup, not sure I'd consider that "compelling", the match variant is easier to read to me, and would benefit from splitting in two nested matches perhaps, for clarity. Or better, an improved domain model. I know it's just an example, but still.

bleis-tift commented 3 years ago

FYI, Idris language has a little bit similar feature.

let Just id = opts.Id | Nothing => () in
...

Above code is same as the following code:

case opts.Id of
  Nothing => ()
  Just id => ...
Luiz-Monad commented 3 years ago

Regarding.

match? opts.Id with Some id -> yield Props.Id id :> IHTMLProp

This example would be perfect, only sometimes I need to disable exhaustiveness on match. Also, I strive really hard to not have "if"s in my code, pattern match all the things ! More data-flow and less control-flow. ( I wish there was a warnings to keep you out from using if, my entire 20Kloc backend has only 10 or so ifs, but ifs are sometimes useful, but I digress )

More examples, this would also work this proposed change of match?, so it disables the exhaustiveness check for unit or any DU when the last case is trivial like None of unit , I think this would be the most useful feature to make code more terse in some cases.

let x = match? opts.Id with Some id -> yield Props.Id id :> IHTMLProp

And if you really want an if, just by making if work automatically on options and introducing a bit of syntax, (stolen from the match itself), to introduce the binding, its possible to achieve a very simple syntax with what we already have. So I propose two changes to make it work: The first one would be accepting options in the if and automatically doing Option.isSome on them, and the second change would be using the as keyword to introduce the result of Option.get as a 'let' binding if the Option.isSome is true.

Examples for the proposed usage would be:

if opts.Id as it then yield Props.Id it :> IHTMLProp  // when opts.Id is of type option<_>
if (match? opts with SomeActivePattern id -> Some id) as someVar then yield someVar |> someMapping  // *1
if (Some 1) as v then v.GetType() |> Dump // "System.Int32"

The syntax on *1 almost already work, except mostly for the binding, I guess that it would be just some syntactic sugar for:

let someVar = (match opts with SomeActivePattern id -> Some id | _ -> None ) in if Option.isSome someVar then yield Option.get someVar |> someMapping  

Or even without parenthesis, but I don't know how ambiguous that would be to parse, I don't mind paying the parenthesis in this case.

if match? opts.Id with Some id -> id as it then yield Props.Id it :> IHTMLProp

The syntax would be very similar to the let syntax, except we are introducing a variable binding not a value with the as. Its also very similar to what match already does, except as introduce it to the block-expression of the if, this way the syntax wouldn't look bizarrely different and would fit better with the rest of the language. Also, no new keywords would need to be introduced. Except for the new variant to match, but I'm not sure if this is a token or keyword or what, as we have match! already its already reserved, so its not a problem.

//things that are already possible
let id = 0 in let y = 1 in 2
match o with  | :? 'T as res -> res
dsyme commented 2 years ago

It's strange, but I literally never feel the need for this feature in this form

  1. if let <pat> = <expr> else <expr> just feels like a bad way of writing a match.

  2. if let <pat> = <expr> - the aim is to have a match with an implicit -> (). If so, then why not add something which keeps the same flow as match, e.g.

match? opts.Id with Some id -> Props.Id id 
match? opts.Value with Some value-> Props.Id value
match? opts.DefaultValue with Some defValue-> Props.Id defValue 
...

This is consistent enough with if .. then allowing the else branch to be elided and () implied.

It also generalises to allow multiple pattern rules with still an implicit _ -> () though those use cases aren't that interesting (why not make the | _ -> () explicit? It's just one line)

match? input with
| Some 0 -> print "hello"
| Some 2 -> print "goodbye"

I'm trying to work out why I prefer this. I think I want the order to be

  1. decide to match
  2. write match?
  3. write the input
  4. write the target pattern
  5. write the result

rather than

  1. decide to match
  2. write if let
  3. write the target pattern
  4. write the input
  5. write the result

which seems back-to-front to me.

Also I'm attached to the principle that let doesn't fail in F# (unless a warning is given for an incomplete match).

Also I like that converting between the two forms doesn't rearrange my code, it jsut removes ? and adds | _ -> ()

I'm not attached to match? - and there are problems with ? which normally implies optionality or nullable. I just can't particularly think of anything better.

TheJayMann commented 2 years ago

Thinking more about this proposal, I realize that C# essentially added this functionality when pattern matching was introduced by expanding the existing is keyword. Prior it was a simple type checking operator, but was expanded to take pattern, match it against an expression, and, if the expression matches the pattern, propagate a true expression, and, if the pattern creates variables, allow access to them from code that will only be executed along the true path.

I like the idea of adding a new operation which can return a boolean value to an instruction which can branch based off of a boolean, such as if ... then and while ... do, making the bound values from the pattern available in the appropriate branches.

If is can be brought over as is, then that would allow the following.

if opts.Id is Some id then Props.Id id 
if opts.Value is Some value then Props.Id value
if opts.DefaultValue is Some defValue then Props.Id defValue 
...

If is is too much of a breaking change, then, perhaps something else, such as match could be used.

if opts.Id match Some id then Props.Id id 
if opts.Value match Some value then Props.Id value
if opts.DefaultValue match Some defValue then Props.Id defValue 
...
abelbraaksma commented 2 years ago

If is is too much of a breaking change

It probably is, because is is not a reserved keyword. You can have let is x = x, and since space has higher precedence, opts.Is is Some id would then have the compiler complain that opts.Id is is of the wrong type (i.e., is being a function and all).

The other 'breaking change" thingy is that we are quite strict about what characters are allowed for infix operators. And operators containing letters are not among them.

vzarytovskii commented 9 months ago

This is probably not, as discussed with Don. Closing it for now @dsyme.

dsyme commented 9 months ago

Yes, I assessed this with Vlad, and this is not something that we plan to add to F#.

If a flow-typing system were added to F# it may be different. But for now, an explicit use of match is the way to approach this kind of elimination.

match x with
| pat -> ...
| _ ->
...
edgarfgp commented 9 months ago

Sad to see this been discarded. I really use and like the if let in Swift