Does Swift support deconstruction of custom types to tuples?

497 views Asked by At

C# has a neat feature where you can decorate your custom types with deconstruction to allow them to automatically deconstruct to tuples.

Take this class...

public class Person {

    public string FirstName { get; set; }
    public string LastName  { get; set; }

    public Person(string firstName, string lastName){
        FirstName = firstName;
        LastName  = lastName;
    }
}

If you add a custom deconstructor to its definition, like this...

public class Person {

    ...

    public void Deconstruct(out string firstName, out string lastName){
        firstName = FirstName;
        lastName  = LastName;
    }
}

You can now use instances of that type where you have a two-string tuple, like this...

var p = Person("John", "Smith");

var (firstName, lastName) = p;

log(firstName); // Prints 'John'

Essentially anywhere that takes a two-string tuple can now take a Person object directly and the deconstructor will work its magic to populate its values. It can even infer the types as it does above.

So does Swift support deconstructors for custom types? If not, is there anything similar?

1

There are 1 answers

4
Corbell On

I'll give a more concrete suggestion based on above comments... I don't believe there's an exact parallel to the deconstruction concept, but there are a few ways to have diverse objects represent themselves in similar ways e.g. through a tuple of strings.

I assume part of the goal here is to be able to pass different types of objects to some methods that want the more generic tuple as input.

One Swift-like way to do this is to use a protocol that declares the tuple representation, and have any object you want to work conform to the protocol. It probably takes nearly as many words to describe as at does just to code. :)

Here's a protocol sample:

protocol TwoString {
    var tupl: (String, String) { get }
}

Here's a simple class that's adopted the protocol to return first and last name:

class Name: TwoString {
    var first: String = ""
    var last: String = ""

    init(first: String, last: String) { self.first = first; self.last = last }

    var tupl: (String, String) {
        get {
            return (self.first, self.last)
        }
    }
}

Another class could adopt the protocol and use a different pair of fields for the tuple, e.g.

class Taxonomy: TwoString {
    var genus: String = ""
    var species: String = ""

    init(genus: String, species: String) { self.genus = genus; self.species = species }

    var tupl: (String, String) {
        get {
            return (self.genus, self.species)
        }
    }
}

Now you can have methods that take the protocol type TwoString without knowing anything about the underlying class, except that it can provide a two-string tuple representation of itself.

If you want to convert objects that you don't originally define to also support the protocol, you can use a Swift extension that declares and implements the protocol on an existing class:

extension Data: TwoString {
    var tupl: (String, String) {
        get {
            return (String(describing:self.count), self.base64EncodedString()) 
        }
    }
}

Now a method like func handleTwoString(_ item: TwoString) could take a Name, Taxonomy, or Data object and get a two-string tuple without knowing anything else about the underlying object.

Or, if you want to work with APIs that take a two-item tuple, you can always just pass the tupl member explicitly:

SomeAPI.handleStringTuple(myName.tupl)
SomeAPI.handleStringTuple(taxonomy.tupl)
SomeAPI.handleStringTuple(someData.tupl)