Rick Clark
Rick Clark

Reputation: 33

Extended capability for a more restrictive generic

I have a generic binary search tree based on Comparable:

public class BSTree<T: Comparable> {
    public func insert(_ val: T, _ n: Int) {
        // ...
    }

    @discardableResult
    public func delete(_ val: T, _ n: Int) -> Int {
        // ...
    }
}

I want to add the ability to provide the sum of the values, if T is an arithmetic type. I tried the following:

public class BSTree<T: Comparable> {
    private var sumStorage: T?

    public func insert(_ val: T, _ n: Int) {
        if let arithVal = val as? AdditiveArithmetic {
            for _ in 0 ..< n { sumStorage += arithVal }
        }
        // ...
    }

    @discardableResult
    public func delete(_ val: T, _ n: Int) -> Int {
        // ...
        numDeleted = ...
        if let arithVal = val as? AdditiveArithmetic {
            for _ in 0 ..< numDeleted { sumStorage -= arithVal }
       }        
    }
}

extension BSTree where T: AdditiveArithmetic {
    public var sum: T {
        sumStorage as? T ?? T.zero
    }
}

Of course, when I try to cast val as AdditiveArithmetic I get “Protocol 'AdditiveArithmetic' can only be used as a generic constraint because it has Self or associated type requirements”. Plus sumStorage isn’t AdditiveArithmetic, so I can’t add to it, and I can’t make it a stored property of the extension, because ... you just can’t.

What I finally came up with was to use inheritance:

class SummedBSTree<T>: BSTree<T> where T: AdditiveArithmetic & Comparable {

    public var sum = T.zero

    override public func insert(_ val: T, _ n: Int) {
        super.insert(val, n)
        for _ in 0 ..< n { sum += val }
    }

    @discardableResult
    override public func delete(_ val: T, _ n: Int) -> Int {
        let numDeleted = super.delete(val, n)
        for _ in 0 ..< numDeleted { sum -= val }
        return numDeleted
    }

}

This works, but it seems like it’s a case of using a sledgehammer where a jeweler’s screwdriver should be able to do the trick. It’s frustrating that something that would be so easy to do in Objective-C (and other less strongly typed languages) is so difficult in Swift. Can someone come up with a way of adding the summing capability without subclassing?

Upvotes: 0

Views: 88

Answers (1)

Vader
Vader

Reputation: 77

import UIKit
//https://stackoverflow.com/questions/61784548/swift-extended-capability-for-a-more-restrictive-generic

protocol ON1Speedable {
    
    associatedtype Item: Comparable

    var sumStorage: Item? { get set }
    
}

public class BSTree<T: Comparable> {
        
    var sumStorage: T?
    
    init(sumStorage: T? = nil) {
        self.sumStorage = sumStorage
    }
    
}

extension ON1Speedable where Item: AdditiveArithmetic, Item: Strideable, Item.Stride: SignedInteger {
    
    mutating func insert(_ val: Item, _ n: Int) {
        sumStorage = sumStorage ?? Item.zero
        for _ in 0..<n { sumStorage! += val }
    }
    
    @discardableResult
    mutating func delete(_ val: Item, _ n: Int) -> Item? {
        sumStorage = sumStorage ?? Item.zero
        for _ in Item.zero..<val { sumStorage! -= val }
        
        return sumStorage
    }
    
}

extension BSTree: ON1Speedable { }

var g2 = BSTree<Int>(sumStorage: 0)

g2.sumStorage
g2.insert(5, 5)
g2.sumStorage // 25
g2.delete(5, 5) // 0

var g3 = BSTree<String>()

g3.sumStorage // nil
//g3.insert("foo", 5) // Error: Referencing instance method 'insert' on 'ON1Speedable' requires that 'String.Stride' conform to 'SignedInteger'
g3.sumStorage // nil
//g3.delete("bar", 5) // Error: Referencing instance method 'delete' on 'ON1Speedable' requires that 'String.Stride' conform to 'SignedInteger'

Upvotes: 0

Related Questions