Generic constraint with not equals

You can do:

protocol SomeView {
    associatedtype ViewModel
}

private function foo<T: SomeView>(view: T) where T.ViewModel == Void {

}

But it's not possible to do:

private function foo<T: SomeView>(view: T, viewModel: T.ViewModel) where T.ViewModel != Void {
     
}

It seems like it's possible to constraint a type to being another type. Then it should be possible to do the opposite. A type is either a type or not, there's no in-between.

or is there some other way of deciding which method can be called based on a type constraint?

1 Like

The problem with negative constraints is that they don't allow you to write any useful generic algorithms. They tell you what a type isn't or can't do, and nothing about what it is or can do (which is what you actually need to know).

You can absolutely add constraints on associated types, though - either at the protocol level or at the algorithm level:

// protocol-level constraint:

protocol ViewModelProtocol {
  associatedtype Element
}
protocol SomeView {
  associatedtype ViewModel: ViewModelProtocol where ViewModel.Element: Hashable
}

// algorithm-level constraint:

protocol SomeView {
  associatedtype ViewModel
}
func doSomething<T>(with view: T) where T: SomeView, T.ViewModel: ViewModelProtocol, T.ViewModel.Element: Hashable {
 // ...
}

I'd like to overload a function when the types are NOT equal.

func doSomething<T>(one: T, two: T) { ... }

func doSomething<T, U>(one: T, two: U)  where T.Type != U.Type { ... }

I'd like the second one to be called only when the types are different.

Is there some way to do this?

That already seems to work.

func doSomething<T>(one: T, two: T) {
    print("Same")
}

func doSomething<T, U>(one: T, two: U) {
    print("Different")
}

doSomething(one: "", two: "") // prints Same
doSomething(one: "", two: 1) // print Different

Now, whether that behavior is guaranteed and how strong that guarantee is in practice, I don't know.

Using overloads, the single-type overload will only be used in cases where it's visible at the call site that both arguments are the same type, since Swift picks a single overload per call site.
You could instead test the value of the types in a condition inside a single function definition:

func doSomething<T, U>(one: T, two: U) {
  if T.self != U.self {
    /*handle different case*/
  } else {
    let twoAsT = unsafeBitCast(two, to: T.self)
    /*handle same case*/
  }
}

I'd like for us to eventually have proper language support for testing type conditions like this in a way that doesn't need the bitcast afterward.

import XCTest
func XCTAssertEqual<V, T>(
        _ expression1: @autoclosure () throws -> V,
        _ expression2: @autoclosure () throws -> T,
        _ message: @autoclosure () -> String = "",
        file: StaticString = #filePath,
        line: UInt = #line
    ) where T: Equatable {
        guard let value = (try? expression1()) as? T else {
            XCTFail("Failed cast from type \"\(V.self)\" to \"\(T.self)\"" + message(), file: file, line: line)
            return
        }
        XCTAssertEqual(value, try expression2(), message(), file: file, line: line)
    }

Here is the real example. I guess this code is selected first instead of the version in XCTest. Because this won't compile:

enum Boom: Equatable { case bang, splash }
let result = Boom.bang
XCTAssertEqual(result, .splash) // Type 'Equatable' has no member 'splash'

And thanks for your quick response. I didn't expect anyone to actually try out the code. Sometimes in tests I have to do two asserts, first to see if the type cast works and then to see if it's equal. I'm trying to do that in one step.

I would also like to keep the nice short syntax of not having to fully specify the type of the second parameter.

Maybe I just change the name of the function instead of doing override.Feels like a fail though :slightly_frowning_face:

Slightly off-topic question

Why do you use unsafeBitCast rather than as! here? Speed?

1 Like

Yeah, since it's the exact same type it should be safe in this instance. as! would work too if you want the added safety check.

2 Likes

This looks cleaner:

func doSomething<T, U>(one: T, two: U) {
    if let twoAsT = two as? T {
        /*handle same case*/
    } else if let oneAsU = one as? U {
        /*handle same case*/
    } else {
        /*handle different case*/
    }
}

Why both if's? – to handle case with subclasses properly:

class A {}
class B: A {}
doSomething(one: B(), two: A())
doSomething(one: A(), two: B())

assuming you want to treat these two lines the same way.

In the end I changed the name instead of overriding.

But what I'm looking for is something that can use the doSomething<T> function.

Because swift has type inference you won't need to fully specify the type of the second argument if they are considered to be the same.

Maybe this can't actually happen though, so I used a different name for doSomethingElse<T, U> and it doesn't look too bad. Maybe it is even better that way on second thought.