Swift Splatting

Splatting in a programming language is the ability to take a function that is variadic (can have an unlimited number of parameters) and treat it like a function that takes an array.

C has a fairly dangerous way of doing this that involves mucking around with a pointer to the last parameter. It’s cute and it works and it’s also decidedly not type-safe.

C# has a way of doing this too, which is to use the params keyword, which allows the last argument to be zero or more arguments of one type. For example:

public int HowManyArgsDidIGet(params int[] args) {
        return args.Length;
}

This will take any number of integers and returns how many were passed in. The syntax, which requires the type to be an array, reveals the implementation: all the parameters will get folded into a single array. C# has the limitation that you can have up to precisely one params argument and it has to be the last argument. One nice thing is that you can also call this method with an array of int. This is called splatting.

Swift also has variadic arguments and they are implemented under the hood identically. One difference between swift and C# is that swift allows you to have as many variadic arguments as you’d like by using the argument label syntax.

The equivalent swift to the C# is:

public func howManyArgsDidIGet(args: Int...) -> Int {
    return args.count // args is an array
}

The problem that arises is that swift does not allow splatting. This is probably because I can also write this function:

public func howManyArgsDidIGet(args: [Int]) -> Int {
    return args.count
}

Which even though they are ostensibly the same function have different signatures.

The problem that arises is when you are writing classic OOP code:

open class NotImportant {
    public init () {
    }
 
    open func howManyArgsDidIGet(args: Int...) -> Int {
        return args.count // args is an array
    }
}
 
public class DoesntWork : NotImportant {
    open override func howManyArgsDidIGet(args: Int...) -> {
        print ("calling super...")
        return super.howManyArgsDidIGet(args: args) // Cannot convert value of type [Int] to expected argument type 'Int'
    }
}

This is a known problem in swift and one that has had a number of suggestions for possible implementations, but it still leaves you high and dry if you need to override a variadic method. Or does it? I’m an old time C programmer so I have absolutely no problems casting something to the type that I know it really is. Swift lets you do that too.

 
public class DoesWork : NotImportant { open override func howManyArgsDidIGet(args: Int…) -> { print (“calling super…”) let clos = unsafeBitCast(super.howManyArgsDidIGet, to: (([Int])->Int).self) return clos(args) } }

What’s going on here? We recognize that even though the signature of the original method is (Int...)->Int, it is 100% equivalent to ([Int])->Int, so I take the method I want to call and cast it as a closure of the type that I want and call it. Ta-da! splatting.

The problem that this doesn’t solve is variadic subscripts, which are also legal in swift. This issue here is that neither the getter nor the setter are accessible from swift as a closure, so this kind of casting doesn’t work.

If you don’t like this kind of casting, just don’t ever use variadic parameters in swift methods that can be overridden. And never under any circumstances use variadic subscripts in overridable methods.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.