I'd like to continue a discussion from here:
I've always wanted this feature and as I just discovered the same workaround that @Karl mentioned in this other related thread, I figured it would be interesting to see if and how this feature could be added to the language
Though it probably doesn't add anything substantial to what has already been said in linked threads, I'll reintroduce the requested feature here via an example of a situation where I really wanted it.
Let's say I have this:
protocol VectorIndex {
// ...
}
protocol Vector: ExpressibleByArrayLiteral, CustomStringConvertible {
associatedtype Element
associatedtype Index: VectorIndex
subscript(index: Index) -> Element { get set }
init(_ elementForIndex: (Index) -> Element)
}
// ...
( More complete VectorIndex )
// FIXME: Make this inherit/refine CaseIterable once SR-12980 is fixed.
protocol VectorIndex {
static var count: Int { get }
var intValue: Int { get }
var wrappedNext: Self { get }
var wrappedPrevious: Self { get }
static var allCases: [Self] { get } // FIXME: Remove once SR-12980 is fixed.
}
enum VectorIndex1: VectorIndex {
static let count: Int = 1
case i0
var intValue: Int {
switch self {
case .i0: return 0
}
}
var wrappedNext: Self {
switch self {
case .i0: return .i0
}
}
var wrappedPrevious: Self {
switch self {
case .i0: return .i0
}
}
// FIXME: Remove once SR-12980 is fixed:
static var allCases: [Self] { [.i0] }
}
enum VectorIndex2: VectorIndex {
static let count: Int = 2
case i0, i1
var intValue: Int {
switch self {
case .i0: return 0
case .i1: return 1
}
}
var wrappedNext: Self {
switch self {
case .i0: return .i1
case .i1: return .i0
}
}
var wrappedPrevious: Self {
switch self {
case .i0: return .i1
case .i1: return .i0
}
}
// FIXME: Remove once SR-12980 is fixed:
static var allCases: [Self] { [.i0, .i1] }
}
enum VectorIndex3: VectorIndex {
static let count: Int = 3
case i0, i1, i2
var intValue: Int {
switch self {
case .i0: return 0
case .i1: return 1
case .i2: return 2
}
}
var wrappedNext: Self {
switch self {
case .i0: return .i1
case .i1: return .i2
case .i2: return .i0
}
}
var wrappedPrevious: Self {
switch self {
case .i0: return .i2
case .i1: return .i0
case .i2: return .i1
}
}
// FIXME: Remove once SR-12980 is fixed:
static var allCases: [Self] { [.i0, .i1, .i2] }
}
enum VectorIndex4: VectorIndex {
static let count: Int = 4
case i0, i1, i2, i3
var intValue: Int {
switch self {
case .i0: return 0
case .i1: return 1
case .i2: return 2
case .i3: return 3
}
}
var wrappedNext: Self {
switch self {
case .i0: return .i1
case .i1: return .i2
case .i2: return .i3
case .i3: return .i0
}
}
var wrappedPrevious: Self {
switch self {
case .i0: return .i3
case .i1: return .i0
case .i2: return .i1
case .i3: return .i2
}
}
// FIXME: Remove once SR-12980 is fixed:
static var allCases: [Self] { [.i0, .i1, .i2, .i3] }
}
( More complete Vector )
protocol Vector: ExpressibleByArrayLiteral, CustomStringConvertible {
associatedtype Element
associatedtype Index: VectorIndex
subscript(index: Index) -> Element { get set }
init(_ elementForIndex: (Index) -> Element)
}
// MARK: CustomStringConvertible
extension Vector {
var description: String {
return "[" +
Index.allCases
.map({ String(describing: self[$0]) }).joined(separator: ", ")
+ "]"
}
}
// MARK: Vector Initialization
extension Vector {
init(arrayLiteral elements: Element...) {
// TODO: Check if this adds overhead in some (maybe even all?) scenarios.
precondition(elements.count == Index.count)
self.init { elements[$0.intValue] }
}
}
extension Vector where Index == VectorIndex2 {
init(_ e0: Element, _ e1: Element) {
self.init {
switch $0 {
case .i0: return e0
case .i1: return e1
}
}
}
}
extension Vector where Index == VectorIndex3 {
init(_ e0: Element, _ e1: Element, _ e2: Element) {
self.init {
switch $0 {
case .i0: return e0
case .i1: return e1
case .i2: return e2
}
}
}
}
extension Vector where Index == VectorIndex4 {
init(_ e0: Element, _ e1: Element, _ e2: Element, _ e3: Element) {
self.init {
switch $0 {
case .i0: return e0
case .i1: return e1
case .i2: return e2
case .i3: return e3
}
}
}
}
// MARK: - Vector Arithmetic
extension Vector where Element: AdditiveArithmetic {
static func +(lhs: Self, rhs: Self) -> Self { Self { lhs[$0] + rhs[$0] } }
static func +(lhs: Self, rhs: Element) -> Self { Self { lhs[$0] + rhs } }
static func +(lhs: Element, rhs: Self) -> Self { Self { lhs + rhs[$0] } }
static func +=(lhs: inout Self, rhs: Self) {
for i in Index.allCases { lhs[i] += rhs[i] }
}
static func +=(lhs: inout Self, rhs: Element) {
for i in Index.allCases { lhs[i] += rhs }
}
static func -(lhs: Self, rhs: Self) -> Self { Self { lhs[$0] - rhs[$0] } }
static func -(lhs: Self, rhs: Element) -> Self { Self { lhs[$0] - rhs } }
static func -(lhs: Element, rhs: Self) -> Self { Self { lhs - rhs[$0] } }
static func -=(lhs: inout Self, rhs: Self) {
for i in Index.allCases { lhs[i] -= rhs[i] }
}
static func -=(lhs: inout Self, rhs: Element) {
for i in Index.allCases { lhs[i] -= rhs }
}
}
extension Vector where Element: Numeric {
static func *(lhs: Self, rhs: Self) -> Self { Self { lhs[$0] * rhs[$0] } }
static func *(lhs: Self, rhs: Element) -> Self { Self { lhs[$0] * rhs } }
static func *(lhs: Element, rhs: Self) -> Self { Self { lhs * rhs[$0] } }
static func *=(lhs: inout Self, rhs: Self) {
for i in Index.allCases { lhs[i] *= rhs[i] }
}
static func *=(lhs: inout Self, rhs: Element) {
for i in Index.allCases { lhs[i] *= rhs }
}
}
extension Vector where Element: FixedWidthInteger {
static func &+(lhs: Self, rhs: Self) -> Self { Self { lhs[$0] &+ rhs[$0] } }
static func &+=(lhs: inout Self, rhs: Self) {
for i in Index.allCases { lhs[i] &+= rhs[i] }
}
static func &-(lhs: Self, rhs: Self) -> Self { Self { lhs[$0] &- rhs[$0] } }
static func &-=(lhs: inout Self, rhs: Self) {
for i in Index.allCases { lhs[i] &-= rhs[i] }
}
static func &*(lhs: Self, rhs: Self) -> Self { Self { lhs[$0] &* rhs[$0] } }
static func &*=(lhs: inout Self, rhs: Self) {
for i in Index.allCases { lhs[i] &*= rhs[i] }
}
static func /(lhs: Self, rhs: Self) -> Self { Self { lhs[$0] / rhs[$0] } }
static func /(lhs: Self, rhs: Element) -> Self { Self { lhs[$0] / rhs } }
static func /=(lhs: inout Self, rhs: Self) {
for i in Index.allCases { lhs[i] /= rhs[i] }
}
static func /=(lhs: inout Self, rhs: Element) {
for i in Index.allCases { lhs[i] /= rhs }
}
}
extension Vector where Element: BinaryFloatingPoint {
static func /(lhs: Self, rhs: Self) -> Self { Self { lhs[$0] / rhs[$0] } }
static func /(lhs: Self, rhs: Element) -> Self { Self { lhs[$0] / rhs } }
static func /=(lhs: inout Self, rhs: Self) {
for i in Index.allCases { lhs[i] /= rhs[i] }
}
static func /=(lhs: inout Self, rhs: Element) {
for i in Index.allCases { lhs[i] /= rhs }
}
}
// MARK: - Inner Products
infix operator .*: MultiplicationPrecedence
extension Vector where Element: Numeric {
/// Returns the scalar product, or dot product (the common special case of
/// the inner product).
static func .*(lhs: Self, rhs: Self) -> Element {
var r: Element = 0
for i in Index.allCases { r += lhs[i] * rhs[i] }
return r
}
}
// ...
Since a square matrix can be represented as eg VecN<VecN<Float>>
(where VecN<Element>: Vector
), I can write generic code for eg computing the determinant of any NxN matrix via an extension like this:
extension Vector where
Element: Vector,
Index == Element.Index,
Element.Element: Numeric
{
func determinant() -> Element.Element {
fatalError("unimplemented")
}
}
(Note: This is about the ability to express abstractions and do generic programming in Swift. Whether or not I should rather use SIMD or BLAS etc for code like this is irrelevant for the purpose of this discussion.)
Let's say I want to implement a special method of computing the determinant for the specific case of 4x4 matrices (because it is faster than the general method above). I can do this like so:
extension Vector where
Element: Vector, // -.
Index == Element.Index, // :-- Note: These again ...
Element.Element: Numeric, // -'
Index == VectorIndex4
{
func determinant() -> Element.Element {
fatalError("unimplemented (faster 4x4-specific)")
}
}
Now, using @Karl's workaround we can (today) factor out the repeated set of constraints from those two extensions like this:
typealias IsSquareMatrix<V> = V where
V: Vector,
V.Element: Vector,
V.Element.Index == V.Index,
V.Element.Element: Numeric
extension Vector where IsSquareMatrix<Self>: Any {
func determinant() -> Element.Element {
fatalError("unimplemented (NxN)")
}
}
extension Vector where IsSquareMatrix<Self>: Any, Index == VectorIndex4 {
func determinant() -> Element.Element {
fatalError("unimplemented (faster 4x4-specific)")
}
}
The requested feature, if/when correctly worked out, should IMO be able to do what this workaround does here, only in a much nicer way of course. I haven't managed to come up with any fantastic concrete suggestions for it's design though.
Sorry for the long post but I felt that it was important to note that IMO the feature should be able to handle scenarios like above (ie both of those extensions), as some of the previous examples/sketches I've seen would not.
Thoughts?