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’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a multi-value equality check #675

Open
EBrown8534 opened this Issue Jun 18, 2018 · 10 comments

Comments

Projects
None yet
6 participants
@EBrown8534

EBrown8534 commented Jun 18, 2018

Add a multi-value equality check

I propose we add a multi-value equality-check, similar to IN for SQL, for example. The idea is that one could do something similar to:

x in {1; 4}

Which could be rewritten (behind the scenes, so-to-speak) to:

x = 1 || x = 4

While there are alternatives (see below), none of these make the intent completely obvious, or create more complexity and cause for errors. (Easy enough to accidentally do x = 1 || y = 4 and miss the second comparison.) This suggestion would add another layer of safety to F#. I purposely chose the syntax with the sequence-style initiator as it makes the most sense: we're comparing x with a sequence of values, and only care that it matches one.

As, the in keyword already has meaning in F# it's likely we'd want to find an alternative. (Maybe {1; 4} has x, et al.) I'm simply proposing the idea here, to help create a more succinct manner of matching a set of elements together.

The existing way of approaching this problem in F# is to create multiple x = a expressions, or pattern-matching, such as:

x = 1 || x = 4

Or:

match x with | 1 | 4 -> true | _ -> false

There is also an alternative using .exists, but I think it starts hiding intent:

List.exists ((=) x) [1; 4]

One can also write this trivially with some sort of function:

let ``in`` s x = Seq.exists ((=) x) s

This complicates the use a bit, and it's easiest to do with List or Array types, but it still makes sense:

x |> ``in`` [1; 4]

I'm sure there are others, but the point is we already have several longer, less-direct ways of testing for this.

Pros and Cons

The advantages of making this adjustment to F# are a more fluid short-hand for comparing direct equality against multiple values, and it can make the intent more clear. ("I want to make sure x is specifically 1 or 4.") Obviously a match can do the same, but is substantially longer and starts obscuring intent with the extra noise.

The disadvantages of making this adjustment to F# are an additional keyword of some-sort, and it will increase compilation complexity. I suspect that there will have to be specific rules for what values can be used here, otherwise it would very-likely substantially increase the work on the compiler. I.e. any type of dynamic-expression in the in clause would create significantly more work, and I assume there are other cons that I have not yet investigated..

Extra information

Estimated cost (XS, S, M, L, XL, XXL): Unsure, but my limited knowledge assumes L or larger.

Related suggestions: N/A

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
@theprash

This comment has been minimized.

theprash commented Jun 18, 2018

There are also the contains functions:

[1; 4] |> Seq.contains x
@EBrown8534

This comment has been minimized.

EBrown8534 commented Jun 18, 2018

Aye, I knew I missed something.

@jwosty

This comment has been minimized.

Contributor

jwosty commented Jun 18, 2018

You can easily add a new function as an alias to it as well, e.g.

let oneOf x xs = Seq.contains xs x
42 |> oneOf [1;2;3]
@EBrown8534

This comment has been minimized.

EBrown8534 commented Jun 18, 2018

@jwosty Thanks, I have already included an example of aliasing for this in my original request. That doesn't make it ideal, and it also doesn't make it quick: performance on Seq.contains or Seq.exists (et. al.) will most certainly be much less than a compiler rewrite. I think it would be a great move for F# in the "Forward" direction, especially with Enumerations:

{Status.Completed; Status.Cancelled} include x

(That was the syntax proposed in Slack, and I like it better than the proposed alternatives.)

@abelbraaksma

This comment has been minimized.

abelbraaksma commented Jun 18, 2018

If you add equality you should think about inequality too, and perhaps the magnitude comparisons. And when you allow the right hand side to take a list, why not taking it one step further and allow lists or sequences on both sides?

I didn't try it, but I'd assume this can be done by creating an operator that operates on two lists, say .= and .<> (overloading the default operators here is discouraged).

@EBrown8534

This comment has been minimized.

EBrown8534 commented Jun 19, 2018

@abelbraaksma That's a fair point, does F# support a < x < b syntax yet? If not, this is a good opportunity, and I'm willing to make a new request for it. Not sure it would make sense to do them like this specific scenario, but I'm not a language designer or anything so I leave that decision to the pros.

The inequality could be done via not ({1; 4} includes x), but it would be nice if the not could go before includes.

If we want to support a facets, I'm down. If we didn't have piping I'd suggest |=, |<>, |<, |>, etc.

@abelbraaksma

This comment has been minimized.

abelbraaksma commented Jun 19, 2018

The more common approach to inequality of sets or sequences is that inequality is true of at least one item of set one is unequal to at least one item of set two, since this is the inverse of equality ( which is true of at least one item compares true).

F# doesn't have a notion of overloaded operators (as opposed to redefined and notto be confused with member overloading of operators), and doesn't have a notion of ternary operators, so the 'between' syntax will not be a light hearted change to the language, if it's even considered (the syntax you gave is valid, but will raise a type exception as Booleans do not have magnitude relationships with integers). You can have multiple arguments though, but then you'd need to write something like x !>> y z assuming that's your operator.

@EBrown8534

This comment has been minimized.

EBrown8534 commented Jun 19, 2018

Interesting, @abelbraaksma.

I'm not a 'functional-' anything, so I defer to your better judgment. I don't know that I'm as interested in set-to-set comparison as I am item-to-set comparisons, which are the more frequent use-case (for me). Ideally, I'd probably think that in the case of {1; 4} includes x, the {1; 4} would be a compile-time list, not a variable. (I.e. let compVals = {1; 4}; compVals includes x would be invalid, as the left side of the includes should be a fixed set, not a variable set.

This would move towards my purpose: a rewrite to x = 1 || x = 4. As it stands, the suggested "bind a new function" will not have nearly the performance of the rewrite, and my particular use-case is in a loop that happens many thousands of times, so it'd be nice if we could filter it out cleanly. (As it stands, we do Seq.filter (fun x -> x <> 1 && x <> 4), but with the includes syntax we could Seq.filter (includes {1; 4} >> not), which seems much more clear. Of course, now it's treated differently and a more complex rewrite would need to happen because there's only one argument.

I'm starting to think this might be fruitless, because we'd especially want composition support, and that substantially complicates implementation.

@EBrown8534 EBrown8534 referenced this issue Jun 19, 2018

Closed

Ternary equality / between comparator #676

4 of 5 tasks complete
@Nhowka

This comment has been minimized.

Nhowka commented Jun 19, 2018

Probably would be better if made it to be somewhat limited but very good at its job. If the set has the comparison constraint, compile it in a way that the number of comparisons is logarithmic. If just equality then rewrite it naively as a sequence of ||. You could pass the result to not, but having another syntax for another usage that is not as friendly would make it worse for reading the code.

@pavelbraginskiy

This comment has been minimized.

pavelbraginskiy commented Jun 21, 2018

Perl 6 sort-of has a syntax for this like if (42 == 30 | 42 | 100 ) { ... }.

Hmm, F# already has something like this: match 42 with 30 | 42 | 100 -> ... | _ -> ().

That's a bit long though. I think that a more useful feature, that covers the usecases of this proposal and potentially many others, would just be a shorthand syntax for checking if a value matches a single pattern, e.g. if 42 =~ 30 | 42 | 100 then ..., to borrow Perl's match operator.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment