Custom comparison operator

is it possible to define your own comparison operator? shall be easy, but how exactly?

my attempt:

infix operator <> : ComparisonPrecedence

func <> <T: Equatable>(a: T?, b: T?) -> Bool {
return a != b
}

func test(a: Error?) {
a <> nil // Binary operator '<>' cannot be applied to operands of type 'Error?' and '_'
(1, 1) <> (0, 0) // Binary operator '<>' cannot be applied to two '(Int, Int)' operands
}

There are a couple of things here.
First, you don't have to write T?, you can use T. It will allow you to compare nil because Optional<T> (which is the actual type for an optional, String? and Optional<String> are the same) can be used as T in your comparison function. If you change your code to:

func <> <T: Equatable>(a: T, b: T) -> Bool {
  return a != b
}

You run into a problem that says Error isn't Equatable because protocols (Error is a protocol) can't conform to protocols. So that makes sense.

The second error Binary operator '<>' cannot be applied to two '(Int, Int)' operands won't go away because tuples aren't Equatable either. You'll need a specific overload of <> that accepts tuples:

func <> <T: Equatable>(a: (T, T), b: (T, T)) -> Bool {
  return a.0 != b.0 && a.1 != b.1
}

Note that the error messages on master are more helpful:

operator.swift:8:5: error: value of protocol type 'Error' cannot conform to 'Equatable'; only struct/enum/class types can conform to protocols
  a <> nil
    ^
operator.swift:3:6: note: required by operator function '<>' where 'T' = 'Error'
func <> <T: Equatable>(a: T?, b: T?) -> Bool {
     ^
operator.swift:9:10: error: type '(Int, Int)' cannot conform to 'Equatable'; only struct/enum/class types can conform to protocols
  (1, 1) <> (0, 0)
         ^
operator.swift:3:6: note: required by operator function '<>' where 'T' = '(Int, Int)'
func <> <T: Equatable>(a: T?, b: T?) -> Bool {
     ^
1 Like

thank you guys,

if i did so then i'd have to make all my types equatable even if they don't have to be.

class C {}

var c: C?
c <> nil // Operator function '<>' requires that 'C' conform to 'Equatable'

i also considered having isNil / isNonNil on Optional type to make nil checks more explicit.

this is the working solution i found:

infix operator <> : ComparisonPrecedence

extension Equatable {
static func <> (a: Self, b: Self) -> Bool {
return a != b
}
}

extension Optional {
static func <> (a: Wrapped?, b: _OptionalNilComparisonType) -> Bool {
return a != b
}

static func <> (a: _OptionalNilComparisonType, b: Wrapped?) -> Bool {
return a != b
}
}

extension BinaryInteger {
static func <> (a: Self, b: Other) -> Bool where Other : BinaryInteger {
return a != b
}
}

don't ask what _OptionalNilComparisonType is... i just copied/pasted it from a similar "func !=" of Optional type.

the int functions were needed as otherwise Int(0) <> Int32(0) complained (and it works with the != version)

so tuples are not Equatable even if we can compare them? weird.

this is very unfortunate and not scalable. but in reality in my code base i've encountered only two instances of such tuple types that i compared with !=, so i just changed the comparison to ~(a == b) (~ - is my analog of !)

given these changes now the only instances of ! in my code base are those associated with something unsafe (be it optional unwrap or implicit optionals or unsafe type cast)

1 Like

That's nice. I'd love to be able to replace != with <> as well. Could you please share the complete working code and all the cases, especially for tuple? Thank you.

I also would like to change &&, ||, and ! with , , and ¬ respectively. If I have to use symbols, I prefer to use the correct symbols.

is a valid operator symbol. Just sayin. :)

4 Likes

that's pretty much it, plus the one for not:

extension Bool {
static prefix func ~(a: Bool) -> Bool {
return !a
}
}

(i don't know how to enter indentation spaces here.)

if you want to have a new operator define it like this:

prefix operator ¬

and then use it in func:

extension Bool {
static prefix func ¬(a: Bool) -> Bool {
return !a
}
}

for infix operations you'd want to specify the precedence.

as for tuples - i don't know how to solve this in a generic manner. my issue was with "!=" and "!" specifically, so when i have this in the code:

someTuple != someTuple2

(which was surprisingly rare) i just changed it to:

~(someTuple == someTuple2)

how do you enter those? i can switch to unicode hex input and type alt + 2227 / 2228, etc - not very convenient. or copy / paste from elsewhere - ditto. i am not very familiar how to make a custom keyboard layout. another way - add those as favourites to Emoji & Symbols window - probably the best one i found so far.

i tried those symbols in the past - not my cup of tea. while i can see a value of making "non screaming" versions of "not" and inequality operators (for example to make those screaming ! more prominent, i'd also like them in red and bold if i could), changing from && to ∧ is probably a step too far on my book.

i got a feeling that others have a similar sentiment in regards to (not) using those non-ascii unicode characters for custom operators. leading us to a situation that that area of swift is not used (and probably wouldn't exist if swift was designed today).

1 Like

On Mac kb layout at least, you conveniently have access to things like ¬≤≥≠÷≈∂∆ via Option+key combinations. No ∧∨ though.

1 Like

Could you please share the complete working code and all the cases, especially for tuple?

You can find source for all of that here.

On my keyboard is four home‐row keystrokes (¤, d, i, s, [...junction]) vs eight much more awkward ones for || (, ⇧⌥¤, ⇧⌥¤, ), so it actually saves me effort. But that is likely not the case for developers who learned their trade in an ANSII‐centric environment.

1 Like

Thank you. I like you're using for inequality operator. I might use that too.

is ¤ the command key? is this a custom keyboard layout?
i tried on german keyboard - it is "alt + 7" for |

[quote="SDGGiesbrecht, post:9, topic:31227"]
You can find source for all of that here .
[/quote]≥|

thank you. now a <> b works for me even for tuples, which is great.

a couple of questions regarding your source:

  • what is @notLocalized and why canada flag for EN?

  • what is @exempt(from: unicode)

  • can not you use "let" for wahr/falsch for some reason?

  • suggestion: in line with ∧ ∨, consider using ⊥ / ⊤ for false / true

  • observation: github site uses a font that makes it hard to tell the difference between v and ∨. on this site the difference is more clearly visible.

  • (and on this site <> is not shown as good as with my Xcode font)

Huh, so orthogonal symbol has another meaning.

if precedingValue == false ∧ followingValue == true {
  return true
} else {
  return false
}

for code like this i scream at my subordinates :slight_smile:. shall be just:

return !precedingValue && followingValue

This seems to be headed off topic, but since it is your own thread, I’ll answer anyway.

It’s a custom layout. (I use too many human languages every day to switch endlessly back and forth.) The ¤ key is a clever dead key that allows me to begin typing the name of a symbol, and as soon as there are enough letters for it to be unambiguous, it is replaced by the symbol itself. It allows punting spellable things like & and @ off of the base layout to make room for more important letters, and it means I don’t really need to remember the locations of infrequently used characters, because I only have to be able to name them. Publishing the code is on my (long) to‐do list.

That’s not relevant to vanilla Swift. They are directives for the documentation generator and proofreader.

  • can not you use "let" for wahr/falsch for some reason?

You can, but I don’t know how stored variables and inlining interoperate across module boundaries. As an @inlinable computed var, it is more certain that the compiler will optimize them back down to just true and false under the hood.

Swift classifies those characters as operators, so they aren’t valid for identifiers. But it was a good idea.

Well that’s embarrassing. Normally so do I.

That code is very old. Since that was written, extensions of unowned types to conform to unowned protocols have proven to be dangerous. I would not have written a Comparable conformance like that today. I haven’t gone to the effort of refactoring it into a wrapper, since I cannot imagine any other ordering for Bool, so it presumably doesn’t matter which conformance is chosen at runtime. But if someone does encounter a bug because of it, it will be dealt with promptly.

I’d be interested in seeing that layout.

I also have a custom keyboard layout with handy dead keys, but it doesn’t have anything quite as fancy as spelling out character names.

On some Mac kb layouts

This operator reminds me of the old good days of Pascal programming language :pensive:

Well… I'm still a Pascal programmer today, using Free Pascal. :blush:

Basic, SQL, Visual Basic, ML, Pascal, Simula, Modula-2, AGOL, Eiffel and several others use <> for inequality testing. I think some spreadsheets do as well.

1 Like

I just uploaded the generator library to GitHub: SDGKeyboardDesign. It isn’t a polished product yet, and it has no documentation, but I’ve seen you around here enough to know you can figure it out on your own. In the interest of responding quickly, I just slapped a copyright notice on it for now, so I can sort out licensing later. But you hereby have permission to put it to personal, non‐commercial use.

In short, initialize a KeyboardLayoutBundle structure however you’d like and call its generate(in:) method to create a layout bundle that can be installed on macOS under Library/Keyboard Layouts and activated in the system preferences. The main initializers do have documentation comments. Specifically, Symbol.key is the key you’ve been asking about. If you have questions, feel free to ask them over on GitHub.

1 Like