# Add a multi-value equality check #675

Open
opened this Issue Jun 18, 2018 · 10 comments

Projects
None yet
6 participants

# 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

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.

• 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 commented Jun 18, 2018

 There are also the `contains` functions: `[1; 4] |> Seq.contains x`

### EBrown8534 commented Jun 18, 2018

 Aye, I knew I missed something.
Contributor

### jwosty commented Jun 18, 2018 • edited

 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 commented Jun 18, 2018 • edited

 @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 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 commented Jun 19, 2018 • edited

 @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 commented Jun 19, 2018 • edited

 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 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 referenced this issue Jun 19, 2018

Closed

#### Ternary equality / between comparator #676

 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.
 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.