Extension methods for non-nominal types

Something that has bothered me for a while is:

  • 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

5 Likes

I have oft wanted to do these too:

extension Any {
    ...
}
extension Any.Type {
    ...
}
4 Likes

Me to - just for a single method:

func configure(block: (Self) -> Void) -> Self {
	block(self)
	return self
}

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

let myView = UIView().configure {
	$0.translatesAutoresizingMaskIntoConstraints = false
	$0.backgroundColor = UIColor(red: 1, green: 0, blue: 0.2, alpha: 0.4)
}
3 Likes

What you're looking for is very similar to Kotlin's static extension members. I imagine it in Swift as

func <T> T.apply(block: T.() -> Void) -> T
// or
static extension<T> T {
  func apply(block: T.() -> Void) -> T { ... }
}

let view = UIView().apply { // this: UIView
  isHidden = false
  backgroundColor = UIColor.black
}

Then again, both this and extending Any have the issue with non-nominal types, a topic that isn't easy to reason about without some strong use cases.

4 Likes

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.

Would this allow us to use tuples as Dictionary keys and Set elements?

1 Like

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.

1 Like

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.

7 Likes

Would the same apply to tuple support in Set and Dictionary?
Secondly what's required to move this topic forward; a draft proposal?

All Set needs from its Element is that it's Hashable. If (Int,Int) were hashable, you could put one in a set with no further work.

A draft proposal won't help make this happen. It's the implementation that's the hard part.

4 Likes

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:

extension<...Elements: Equatable> (Elements...): Equatable {}

(Example from generics manifesto)
Oh what a day it will be when we can write code like that

11 Likes

Aren’t tuples equable up to 6? Are we able to do the same with hashable?

They support the == operator but do not conform to Equatable.

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
3 Likes

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?

Separate topic, please can we not muddy this discussion with a feature that really deserves its own thread.

1 Like

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.

10 Likes

Thanks, yeah, that finally makes sense; in both C# and Kotlin, Tuples and Functions are nominal types hence they could exploit it and Swift couldn't.

Surely this is a discrepancy, that when spare bandwidth presents, needs to fixed. i.e. ideally with no differentiation between the two.

I trust this is not something that Swift's ABI is going to make more difficult to accomplish..

1 Like

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.

7 Likes

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.