Search code examples
swifttypesanydynamic-typing

Handling Dynamic Outputs in Swift Functions Without Sacrificing Type Safety (Any)


I have a basic custom structure in Swift where using Any for input types is less than ideal due to the language's statically typed nature, which can bypass the compiler's type checking and lead to errors and loss of type information.

However, I've encountered a need to return Any from functions like vectorAssoc, particularly for cases where the return type could be either a tuple or a boolean. Here's an example of the Vector and vectorAssoc functions:

struct Vector<Element>: ExpressibleByArrayLiteral {
    private var storage: [Element] = []
    
    public init(arrayLiteral elements: Element...) {
        storage = elements
    }
    
    func count() -> Int {
        return storage.count
    }
    
    subscript(index: Int) -> Element {
        return storage[index]
    }
}

func vectorAssoc(_ v: Int, _ vec: Vector<(Int, Int)>) -> Any {
    func helper(_ i: Int) -> Any {
        if i >= vec.count() {
            return false
        } else {
            let elem = vec[i]
            
            if elem.0 == v {
                return elem
            } else {
                return helper(i + 1)
            }
        }
    }
    
    return helper(0)
}

let vec: Vector = [(2, 1), (3, 1), (4, 1), (5, 1)]

// Example usage
let a = vectorAssoc(4, vec) // prints (4, 1)
let b = vectorAssoc(12, vec) // prints false

Since the output of vectorAssoc may be used as input for another function, I'm seeking suggestions on how to handle the return type Any more effectively. Ideally, I'm looking for a way to specify that the return type can only be one of two specific types (either a tuple or boolean) without resorting to custom types or optionals.

If there's a method to transform such an Any into a primitive type that takes advantage of Swift's type safety features, I would greatly appreciate any insights or improvements to this setup.


So a very basic explanation for the new question is:

Any in this case is ONLY 2 types (tuple and boolean), it cannot be more than two. If there was a way to return Any(only two specific types) and somehow that Any will become a primitive type(one of those 2) that could take advantage of the type safety thing. Is there a way to make this work?


Edit:

But it would be nice to have a new type, something like:

func foo(_ param: in Type) -> out Type { ... }

This type would be similar to Any in that it can represent multiple types, but in this case, it would be restricted to only two types: a tuple and a boolean. You need to specify both in and out because this type can be returned from one function and passed into another.

Another approach could be:

func foo(_ param: Int) -> out Type { ... }

The goal is to avoid wrapping and unwrapping values, using optionals, or dealing with other complications. Ideally, you would get a primitive type out of it directly, simplifying the handling of multiple possible return types.

So (-> out Type) it has the possibility to be one primitive type when passed to another function. Type will work on the same principles as primitive types, but if you still want Type to be multiple types use (_ param: in Type) but is only two types, not like Any.


Solution

  • First of all, as you must've noticed, Swift, unlike Python, has a strong type system, including function return types, and it's not possible to implement what you imagine. But you have other options.

    Do I understand correctly, that the boolean return type is just to indicate an error? If so, why not throwing an error expicitly?

    func vectorAssoc(_ v: Int, _ vec: Vector<(Int, Int)>) throws -> (Int, Int)
    
    do {
        print(try vectorAssoc(4, vec))
    } catch {
        print(false)
    }
    

    If it's not about errors, but rather returning one option of a limited set of options, Swift has enums for this purpose:

    enum ReturnOption {
        case bool(value: Bool)
        case int(value: Int)
    }
    
    func vectorAssoc(_ v: Int, _ vec: Vector<(Int, Int)>) -> ReturnOption
    
    switch  vectorAssoc(4, vec) {
        case .bool(let value),
             .int(let value):
            print(value)
    }
    

    If you want only two states, a value and a missing value, Swift has Optional for this:

    func vectorAssoc(_ v: Int, _ vec: Vector<(Int, Int)>) -> (Int, Int)?
    
    if let result = vectorAssoc(4, vec) {
        print(result)
    } else {
        print(false)
    }