2

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?

Mark A. Donohoe
  • 28,442
  • 25
  • 137
  • 286
  • I don't know of any automatic conversion, though it's trivial to make a computed var that returns a tuple of some or all fields of a class. One might define a simple protocol with computed tuple var and define methods to take instances of the protocol - that way different types of objects could provide tuple data to common methods. Note however that a tuple (like a struct) is not an object and not hashable, so there are many ways it *can't* be used, where an object can. – Corbell Jun 18 '20 at 00:38
  • That's the opposite direction of what I'm talking about. That's *construction*, I'm talking about *deconstruction*... going from an object or a struct to a tuple by an explicit set of rules. – Mark A. Donohoe Jun 18 '20 at 00:47
  • 1
    You should post that as an answer, not a comment. Not only is it (much!) more readable, but I can't 'accept' a comment either, if it works. Don't know yet because I can't read it! ;) – Mark A. Donohoe Jun 18 '20 at 01:04

1 Answers1

1

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)
Corbell
  • 1,283
  • 8
  • 15
  • Interesting approach, but this requires the consuming APIs to know about `TwoString`. As @LeoDabus above suggested, it may just be exposing a tuple as a property (possibly via an extension) and using that. Not as clean as C#, but something. – Mark A. Donohoe Jun 18 '20 at 01:08
  • Yes, you can add a tuple computed-variable to anything, but the protocol is meant to achieve the goal of passing an object as-is into an interface (rather than a property of an object) - in the words of your initial question, "You can now use instances of that type where you have a two-string tuple". Simply adding a property doesn't achieve that, though it does save a little code. – Corbell Jun 18 '20 at 01:40
  • Agree, completely, but the issue with the protocol approach over the variable approach is I can pass the variable to an already-existing APIs that take tuples whereas I can’t with the protocol. Is it perfect? No, but it is usable. There’s no way however to use the protocol unless I myself am writing the API to take it, and in that case, yes, a protocol would absolutely be the way to go. But again, that’s a protocol, not a tuple. The tuple is the important part here. Shame Swift doesn’t support destructuring. – Mark A. Donohoe Jun 18 '20 at 01:42
  • Got it - I revised my answer to show that it also works fine with API's that take a string tuple. Basically this answer is a superset of the minimal answer - it includes the computed var but also adds some additional type safety if you want it. Also maybe worth noting - an adatper extension from protocol to the plain tuple would be easy to add esp. if it isn't a huge API. But yeah there's a similar thing I want based on C++, declaring a type operator to automatically cast one object type to another, which we don't quite have in Swift AFAIK. – Corbell Jun 18 '20 at 01:54