Is it possible to type annotation for some tuple?

Is it possible to create a type annotation for a tuple without being specific on the exact tuple type?

I'm trying to write a function that accepts a tuple and depending on the type of tuple, decides which func to return. Something along the lines of this:

func chooseBuilder(withInput someTuple: /**some tuple*/) -> (/**some tuple*/) ->Void {
    switch someTuple {
    case is (Int, String):
        return funcOne
    case is (Double, Double):
        return funcTwo
    default:
        return funcDefault
    }
}

I suppose I could use generics for this task but that doesn't completely satisfy what I'm looking for. I'm looking for a way to ask for any tuple.

Unfortunately, I think the answer is no. Tuples are statically defined.

You might define a Variant type (instead of Any) which is an enum of different kinds of types and associated values. If the number of arguments needs to be variable, you would probably want to use an array.

E.g.

enum Variant { 
  case int(Int)
  case string(String)
  case double(Double)
}

func chooseBuilder(withInput input: [Variant]) -> [Variant] { }

Maybe someone else has another better suggestion.

1 Like

That might be the solution I’m looking for!

Part of the reason I was leaning toward using tuples was because I need a heterogeneous collection.

After trying it, I don't think this is what I'm looking for. I've tried a whole bunch of approaches involving tuples and enums and I keep finding roadblocks.

First I tried something like this:

enum DraftsURLParams {
    case searchParams(query: String?, tag: String?)
}

func chooseBuilder(withInput someTuple: DraftsURLParams) -> (DraftsURLParams) ->Void {
    switch someTuple {
    case is DraftsURLParams.searchParams: //error: Enum case 'searchParams(query:tag:)' is not a member type of 'DraftsURLParams'
        return funcOne
    default:
        return funcDefault
    }
}

Then I realized oh yeah, the enum case is not interpreting (query: String?, tag: String?)as a tuple. I think it's interpreting it as two associated values of type String? named query: and tag: respectively. So then I tried something like this:

enum DraftsURLParams {
    case searchParams = (query: "query", tag: "tag")
} //Raw value for enum case must be a literal

Doesn't work. Apparently a literal tuple doesn't count. See Enums with multiple raw values

In any case, even if raw values worked, it's not what I'm looking for since I want to be able to change the value of query and tag and not have them hard coded in.

For your approach of using an Array like [Variant], I like this idea but there are still some issues that I haven't wrapped my head around. This does satisfy my need for a heterogenous collection, but now I lose key value pairs. Plus arrays can change size.

The search continues.

Could you clarify what aspects of this task generics don't satisfy? Also, when you say "any tuple," do you really mean any tuple (as in, any arity)?

@Jumhyn,

  • Maybe generics could satisfy, but I don't know how to say I want generic type T where T is a tuple.

  • yes I really mean "any tuple". The idea of the chooseBuilder func I'm trying to implement is to read the type of the tuple and return a func depending on the type of the tuple that was input.

I don't know what

(as in, any arity)

means.

arity = number of members of tuple

2 Likes

As @jonprescott says, arity is the number of members of the tuple. So my question, phrased more explicitly, is, "when you say 'any tuple,' do you really mean tuples with any number of elements (e.g., including (Int, String, Double)), or are you only considering tuples with two elements?"

If you're only concerned about dealing with two-element tuples, you could write something like this:

func chooseBuilder<T, U>(withInput someTuple: (T, U)) -> ((T, U)) -> Void {
   let funcOne: ((Int, String)) -> Void = { print($0.0, $0.1) }
   let funcTwo: ((Double, Double)) -> Void = { print($0.0, $0.1) }
   let funcDefault: ((T, U)) -> Void = { _ in print("Default") }

   switch someTuple {
   case is (Int, String):
       return funcOne as! ((T, U)) -> Void
   case is (Double, Double):
       return funcTwo as! ((T, U)) -> Void
   default:
       return funcDefault
   }
}

Note: it looks like this results in some (incorrect) warnings about "cast will never succeed," but functionally it works as expected. Not sure whether the incorrect warnings are a known issue...

2 Likes

Wow this is great pretty much what I was looking for.

Ideally I would prefer to accept tuples of varying length into the same func. But perhaps I might be able to write a version of a func for each length of tuple.

I think there’s even a way to overload a func depending on the input given so that swift will pick the correct func depending on the input given. If that works the way I’m thinking then I could write a ‘chooseBuilder’ for each length of tuple and swift will just pick the correct one.

That's the current best way to go. The Swift standard library, SwiftUI and Combine are structured that way:

When/if variadic generics will be available, you'll be able to generalize.

1 Like
Terms of Service

Privacy Policy

Cookie Policy