CustomStringConvertible description using a formatted generic type

I have a Vector struct with a generic type T that adheres to the FloatingPoint protocol. I would like to provide a custom description for printing formatted values from the Vector. The example below only works for the Double type. I can't make another extension for the Float type because there can't be another conformance with CustomStringConvertible. How do I format the generic T in the CustomStringConvertible description for both Double and Float types?

public struct Vector<T: FloatingPoint> {

    public var values: [T]

    public init(_ values: [T]) {
        self.values = values
    }

    public init(size: Int, fill: T = 0.0) {
        self.values = [T](repeating: fill, count: size)
    }

    public subscript(item: Int) -> T {
        get { return values[item] }
        set { values[item] = newValue }
    }
}

extension Vector: CustomStringConvertible where T == Double {
    public var description: String {
        var des = [String]()
        for val in self.values {
            des.append(val.formatted(.number.precision(.significantDigits(2))))
        }
        return "<\(des.joined(separator: " "))>"
    }
}

I ended up using the approach shown below:

extension Vector: CustomStringConvertible {

    public var description: String {
        var des = [String]()
        for value in self.values {
            if let val = value as? Double {
                des.append(val.formatted(.number.precision(.fractionLength(2))))
            } else if let val = value as? Float {
                des.append(val.formatted(.number.precision(.fractionLength(1))))
            } else {
                des.append("\(value)")
            }
        }
        return "<\(des.joined(separator: " "))>"
    }
}
1 Like
struct Vector<T: FloatingPoint> {
  var values: [T]
}

extension Vector: CustomStringConvertible where T: StringConvertibleScalar {
  var description: String {
    "<" + values.map(\.scalarDescription).joined(separator: " ") + ">"
  }
}

protocol StringConvertibleScalar {
  var scalarDescription: String { get }
}

extension Float: StringConvertibleScalar {
  var scalarDescription: String { formatted(.number.precision(.fractionLength(1))) }
}

extension Double: StringConvertibleScalar {
  var scalarDescription: String { formatted(.number.precision(.fractionLength(2))) }
}

print(Vector<Float>(values: [1.234, 2.345])) // <1.2 2.3>
print(Vector<Double>(values: [1.234, 2.345])) // <1.23 2.34>
2 Likes

This looks much more clever than the solution I came up with. Can you explain the benefits of doing it this way compared to my solution? I assume it runs faster since there are no if-let statements. Also, why did you use values instead of self.values and formatted instead of self.formatted ?

1 Like

Can you explain the benefits of doing it this way compared to my solution?

It's less and more efficient code. There's no need to do optional typecasts at runtime.

why did you use values instead of self.values and formatted instead of self.formatted ?

Just less noise. The self. is implicit.

1 Like

Thank you for the explanation. I need to learn more about protocols in Swift. They seem to be an important feature of Swift, especially when developing a Swift package.

1 Like