Generic functions on a collection of Measurements

Hi!
I have a bunch of categories on Collection for calculating an average(). Now I want to have the same for Measurement<U>, but can't find out how to do it in a generic way. This is as close as I got, but it's a global function, not a generic extension of Collection:

func average<CollectionType: Collection, UnitType: Dimension>(_ collection: CollectionType, unit: UnitType) -> Measurement<UnitType>? where CollectionType.Element == Measurement<UnitType> {
    if collection.count == 0 { return nil }
    let sum = collection.reduce(Measurement(value: 0, unit: unit)) { state, element in
        state + element.converted(to: unit)
    }
    return sum / Double(collection.count)
}

A generic approach would be something like

extension Collection<D> where Element == Measurement<D>, D: Dimension {
…
}

what isn't currently possible in Swift.

Thanks to @benjamin_herzog for his help so far on arriving there.

Is there any other idea on how to achieve this in a Collection?

Thanks to @graskind we found a way to introduce a protocol which exposes the generic type as an associated type to make it work. The same procedure can be used to create similar solutions for Optional.
In the long term, it would be great to get access to the generic types of a type for type constraints like this.

protocol MeasurementType {
    associatedtype UnitType: Unit
    var measurement: Measurement<UnitType> { get }
}
​
extension Measurement: MeasurementType {
    var measurement: Measurement<UnitType> { return self }
}
​
extension Collection where Element: MeasurementType {
    func average<D: Dimension>(dimension: D) -> Measurement<D>? where Element.UnitType == D { … }

I usually solve these issues by making the extension less constrained than desired, but reaching the desired level of constraints on the methods. e.g.

extension Collection {
	func average <UnitType: Dimension> (in unit: UnitType) -> Measurement<UnitType>? where Element == Measurement<UnitType> {
		guard !isEmpty else { return nil }
		let sum = reduce(Measurement(value: 0, unit: unit), { $0 + $1.converted(to: unit) })
		return sum / Double(count)
	}
}

let values: [Measurement<UnitLength>] = [
	.init(value: 5, unit: .meters),
	.init(value: 5, unit: .yards),
	.init(value: 5, unit: .miles),
	.init(value: 5, unit: .kilometers),
]
print(values.average(in: .meters) ?? 0)

The extension is on all Collection, but average(in:) can be called only on fitting ones.

2 Likes
Terms of Service

Privacy Policy

Cookie Policy