Variadics and ~Copyable

So I'd like to variadically create a vector type (in the linear algebra sense). The idea is that I'd like to do something like:

func makeVector<each T>(_ pack: repeat (each T)) -> (repeat each T) {
    (repeat (each pack))
}

where I enforce that all the T's must a) be the same type and b) be ~Copyable. I can't seem to find any syntax that will meet that requirement. The idea is to be able to enforce at least some of the linear algebra requirements of a vector via the type system. Any advice welcome.

You can't yet write each T: ~Copyable, even with the experimental feature NoncopyableGenerics. Support for noncopyable tuples and packs are expected in the future, they're just not yet implemented.

Even if we did support each T: ~Copyable, keep in mind that you'd only be removing a Copyable requirement from that generic parameter, which broadens the kinds of types you may end up receiving. It doesn't prevent you from having a copyable type like Int from being substituted.

1 Like

Understood. And yes generally I expect I will be receiving Copyable types, but I want to use this to make sure that I make no assumptions about copyability in the underlying code. Originally I was hoping to be able to make "fake dependent types" through this mechanism, but I've hit a wall or two there and am brushing up on my macro skills for that.

2 Likes

Kavon addressed point b), but also a) is not yet supported either unfortunately. It's called a same-element requirement, written as <each T, U> ... where repeat each T == U, but it's not yet implemented.

1 Like

cc: @kavon

So for the record, below is what I ended up with. Sorry for the expository length, but it all seems necessary to get it to compile. Honestly, I'm a bit surprised that this actually works. :)

What I'm really curious about are the lines like this:

.init((repeat constant(each V.zero.vectorStorage as? Scalar)(element)) as! (repeat each U))

Again, the idea is to create a noncopyable type wrapped around a fixed size buffer of homogeneous types that is amenable to manipulation with SIMD. The interesting feature here that is reminiscent of dependent types is that I need the template instance V.zero.vectorStorage to provide the mechanism for me to iterate through the tuple and produce the constant value at each step. The type casting makes me a little sad, and is what has me worried about performance as I'm not sure what's happening there at run time.

I'm using this particular bit to initialize vectors of lengths around 1024 and I'm wondering if I'm better off from a performance point of view to just have the macro spit out the unrolled loop and let object file size and compile time be damned.

protocol VecStorage: ~Copyable {
    associatedtype Scalar: FloatingPoint
    associatedtype Storage
    static var dimension: Int { get }
    static var zero: Self { get }
    var vectorStorage: Storage { get set }
    init(_ repr: consuming Storage)
}

// Example type generated by macro
/*@VectorStorage<Float16>(count: 4)*/
struct T4Float16: VecStorage {
    typealias Storage = (Float16, Float16, Float16, Float16)
    typealias Scalar = Float16

    static let dimension: Int = 4
    static let zero: Self = .init((Float16.zero, Float16.zero, Float16.zero, Float16.zero))

    var vectorStorage: Storage

    @inlinable  init(_ repr: Storage) {
        self.vectorStorage = repr
    }
}

struct Vec<V: VecStorage>: ~Copyable {
    typealias Scalar = V.Scalar
    @inlinable  static var dimension: Int { V.dimension }
    @inlinable  static var zero: Self { .init(V.zero) }

    var value: V

    init(_ value: consuming V) { self.value = value }
    init(_ value: consuming V.Storage) { self.value = .init(value) }
}

func constant<T>(_ t: T?) -> (T) -> T { { $0 } }
func next<S: IteratorProtocol<T>, T>(_ t: T?) -> (inout S, T) -> T { { s, t in s.next() ?? t } }

extension Vec {
    init<each U>(values: repeat each U) where (repeat each U) == V.Storage {
        value = .init((repeat each values))
    }

    init<each U>(
        repeating element: Scalar = Scalar.zero
    ) where (repeat each U) == V.Storage {
        value = .init((repeat constant(each V.zero.vectorStorage as? Scalar)(element)) as! (repeat each U))
    }

    init<each U>(
        sequence: some Sequence<Scalar>
    ) where (repeat each U) == V.Storage {
        var it = sequence.makeIterator()
        value = .init((repeat next(each V.zero.vectorStorage as? Scalar)(&it, Scalar.zero)) as! (repeat each U))
    }
}