Why does Swift not support extension methods on non-nominal types?
For example:
Tuples, naturally with generics would be preferred
extension (Int, Int) {
....
}
Functions, similarly with generics
extension (State, Action) -> State {
....
}
C# extension methods for example provide for this, and its a useful way to easily tag on functionality, in the same way it is today with Swift extension methods on nominal types.
Question has this been discussed before? The only pitch I've seen mentioning non-nominal and extension was unrelated to this more general topic.
**EDIT: Related non-nominal restrictions
The same non-nominal restrictions also appear to be also related to the following limitation:
Note: in C# tuples are supported in both HashSet and Dictionary
So far, NSObjectProtocol seems to be the best workaround for the current limitations:
The most important use case is to replace the "define a closure and call it right away" pattern for configuring instance variables like
Exactly same concept... C# static extension methods could probably have been prior art for Kotlin?. In C# they were introduced alongside Linq; which basically was formulated by in large using static extension methods.
As for it's usefulness in Swift; I'd argue it's for many of the same reasons we have extension methods on nominal types today.
Plus I can essentially use custom generic operators in Swift to mirror the same outcome for both nominal and non-nominal types, but extensions are restricted to only nominal. Seems such a pity, because I can imagine not everyone is crazy about interspersing their code with custom operators.
Note sure; but what's clear is that the implementation of tuples in Swift is quite different to how they are implemented in C#;
in C# (1, 2) is just syntactic sugar for
Tuple.Create<int, int>(1, 2); i.e. it's a class.
It quite similar for function types in C#; which are generic delegates e.g.
delegate R Func<in T, out R> (T t);
Example of Hashset with tuples in C#:
var l = new List<(int, int)> { (1, 1), (2, 2), (1, 1), (3, 3), (2, 2) };
var h = new HashSet<(int, int)>(l); // (1, 1),(2, 2),(3, 3)
Examples of Dictionary with tuples in C#:
var map = new Dictionary<(int, int), string>();
map.Add((1, 1), "apple");
map.Add((2, 2), "pear");
These implementation differences are likely what's underpinning the non-nominal limitations in Swift re extension, Set and Dictionary. So it certainly appears these two concepts are related to the same underlying implementation, and probably should be raised at the same time.
No reason other than that someone needs to design and implement the feature. It's cited as one of the desirable but major extensions to the generic model in the generics manfesto.
I'd rather see variadic generics and parameterized extensions before seeing extensions of non-nominal types. Quite frankly, there isn't much generic expressiveness if we added this before the other two. We couldn't even write hacky code like this because of needing Equatable on a generic constraint:
extension (Equatable): Equatable {}
extension (Equatable, Equatable): Equatable {}
extension (Equatable, Equatable, Equatable): Equatable {}
// and so on
If we get parameterized extensions such code would be possible, but still poor in terms of code quality:
extension<T: Equatable> (T): Equatable {}
extension<T: Equatable> (T, T): Equatable {}
extension<T: Equatable> (T, T, T): Equatable {}
// and so on
Whereas if we have variadic generics and parameterized extensions before this, we could write much nicer generic code:
Tuples are not Equatable. The standard library has to implement == for tuples like this:
public func ==<T: Equatable>(lhs: (T, T), rhs: (T, T)) -> Bool {
// implementation here
}
public func ==<T: Equatable>(lhs: (T, T, T), rhs: (T, T, T)) -> Bool {
// implementation here
}
// and so on up to 6
func checkIfBothAreEqual<T: Equatable>(lhs: T, rhs: T) -> Bool {
return lhs == rhs
}
checkIfBothAreEqual(lhs: (16), rhs: (16)) // This does not work
Confused as to why would this be so difficult, when it's already possible to write generic operators for non-nominal types. Surely there's some similarity between the two? or am I missing something obvious?
One of the difficulties is how protocol conformances are implemented in the compiler. If you dig around in include/swift/AST/ProtocolConformance.h, there's a hierarchy of ProtocolConformance classes that determine how various types conform to protocols (for example, does a type conform directly, or does it conform via its inheritance to another type that conforms to it, or does it conform via a specialization of a generic type that declares conformance to it).
It was recommended by one of the core team members that one could add a TupleProtocolConformance that would define a conformance derived from the conformance of its elements. (Of course, this derivation only makes sense for protocols like Equatable and Hashable, and you'd still want to be able to make an arbitrary type T conform to an arbitrary protocol P without any derivation, but rather by just providing the implementation as you would any other protocol extension.)
I tried diving into this and quickly found that one big problem is that all of those classes are written with the assumption that you can walk up the hierarchy to a root conformance that is on a nominal type. Removing that assumption rippled quite a bit through the rest of the implementation and broke a lot of things, and unfortunately I didn't have the bandwidth to dig into it further.
I think that the current situation makes tuples really hard to work with in Swift. For examples, due to tuples not being Equatable, they can't really be used in a good way with libraries like Nimble, e.g. the following won't work:
expect(("a", "b")).to(equal(("a", "b")))
But more generally this applies to any kind of code that you might naturally want to extend to tuples. It also means that, while individual tuples can be checked for equality, arrays of tuples can't, etc.
Tuples are the most boilerplate-free version of product types and thus incredibly useful; the fact that they're so limited is really a big weakness of Swift currently, IMHO.
It's probably not feasible anymore to have an anonymous counterpart for each entity (method, class, struct, enum...), but having tuples with extensions would even be useful if they aren't anonymous anymore:
typealias Point2D<T: Numeric> = (x: T, y: T)
I don't think return (x: 4, y: 2) is a big win over return Point2D(x: 4, y: 2) - but structural types are compatible as long as their structure is identical, so you wouldn't need to agree on a common Point-lib and still be able to use values from one library with functions from an unrelated framework.