I'm trying to make a Matrix type where its values can be Int, Float, or Double. So I defined the Matrix with a generic type named T so it can accept values as Int, Float, or Double (see the example below). However, this approach doesn't work when I use functions from Accelerate like vDSP.add() because the type information for the Matrix values aren't passed to the Accelerate function. Any suggestions on how I should convey the type information to Accelerate functions? I'm new to using generics with Swift so ideas for a better approach are welcome.
import Accelerate
struct Matrix<T> {
let rows: Int
let columns: Int
var values: [T]
init(rows: Int, columns: Int, values: [T]) {
self.rows = rows
self.columns = columns
self.values = values
}
init(rows: Int, columns: Int, fill: T = 0) {
self.rows = rows
self.columns = columns
self.values = Array(repeating: fill, count: rows * columns)
}
subscript(row: Int, column: Int) -> T {
get { return values[(row * columns) + column] }
set { values[(row * columns) + column] = newValue }
}
static func + (lhs: Matrix, rhs: Matrix) -> Matrix {
let v = vDSP.add(lhs.values, rhs.values)
return Matrix(rows: lhs.rows, columns: lhs.columns, values: v)
}
}
You'll need to move that + into an extension which constrains T down to one of the supported types, e.g.
extension Matrix where T == Double {
static func + (lhs: Matrix, rhs: Matrix) -> Matrix {
let v = vDSP.add(lhs.values, rhs.values) # Calls https://developer.apple.com/documentation/accelerate/vdsp/3240823-add
return Matrix(rows: lhs.rows, columns: lhs.columns, values: v)
}
}
extension Matrix where T == Float {
static func + (lhs: Matrix, rhs: Matrix) -> Matrix {
let v = vDSP.add(lhs.values, rhs.values) # Calls https://developer.apple.com/documentation/accelerate/vdsp/3240825-add
return Matrix(rows: lhs.rows, columns: lhs.columns, values: v)
}
}
This has the added benefit of making it impossible to call + on matrices of types unsupported by Accelerate (e.g. integers).
Optionally, you can add an unconstrained extension with a catch-all + operator (implemented using manual for loops or whatever), but there's a risk there of having a hidden performance trap in your code, that sneaks by unnoticed.
Ok this works, but it's a lot of duplicate code where the body of each extension is the same. Seems like there would be a cleaner way to do this especially to handle a lot of operations like multiplication, division, subtraction, scalar-to-matrix operations, etc.
Yes, dimensions should be checked with a precondition but this question is about handling a generic type; not about checking matrix dimensions. But thanks for the suggestion.
Could you make a macro applied to one version of the method and it produces the other type versions? I haven’t made a macro before, maybe it’ll be as much work as manually duplicating
// not in Swift currently
extension Matrix where T == Float || T == Double {
static func + (lhs: Matrix, rhs: Matrix) -> Matrix {
let v = vDSP.add(lhs.values, rhs.values)
return Matrix(rows: lhs.rows, columns: lhs.columns, values: v)
}
}
For simple cases like this, this might be nice, but in the general case you end up with the C++ template substitution nightmare scenario. Each call needs to be tried against any of the possible variable bindings, and it gets pretty hard to reason about, pretty quick.