Arithmetic with Generics and Protocols

[亡魂溺海] 提交于 2020-05-16 08:38:59

问题


Ahoy Everyone,

I have been working on a Node system for the last few nights, and have hit a bit of a road block (again XD)

Below is a large chunk of code that handles Plugs ( which can be made up of different data types), and Nodes which hold the plugs, and can do something with the data from the plugs. It was copied out of a playground, so should "work" if pasted into one.

I am currently stuck at the execution function in the AddNode, which would access the data from two plugs, add them together and then set that data to the output plug. The Node should be able to handle being instantiated with different types, So it uses Generics.

You can see a bunch of commented out lines as I have been trying different scenarios. I thought I would try and store the Generic Type, and then could use that to cast the value and then add them together, but it seems that does not work.

I am still very new to the world of Swift, this is my trial by fire and learn project. If you notice any bad/smelly code, or something that could be done better please do mention it.

Please ignore some of the comment ramblings, as I code I hit scenarios and wonder if they could be done better, or have a better solution.


protocol PlugValue {
    init()
}

extension Int: PlugValue { }
extension Float: PlugValue { }
extension Double: PlugValue { }
extension SIMD3: PlugValue where Scalar == Int32 { }
//extension Array: PlugValue {}
extension Array: PlugValue where Element: PlugValue {}

class Plug<Value: PlugValue> {
    public var value: Value
    public var name : String

    init(_ value: Value) {
        self.value = value
        self.name = "un-named"
    }

    init(_ value:Value, name:String){
        self.name = name
        self.value = value
    }
}

protocol AnyPlug:class {
    var anyValue: PlugValue { get set}
    var name: String { get set }
}

extension AnyPlug {
    subscript <Value: PlugValue> (type: Value.Type = Value.self) -> Value {
        anyValue as? Value ?? .init()
    }

    func callAsFunction<Value: PlugValue>(_ type: Value.Type = Value.self) -> Value {
        anyValue as? Value ?? .init()
    }

}

extension Plug: AnyPlug {
    var anyValue: PlugValue {
        get {
            value
        }
        set(v) {
            value = v as! Value
        }
    }

    //var anyValue: PlugValue { value }
}

extension Plug {
    enum Error: Swift.Error {
        case typeMismatch
    }
}

extension AnyPlug {
    func callAsFunction<Value: PlugValue, Return>(_ closure:(Value) -> Return) throws {
        guard let value = anyValue as? Value
            else { throw Plug<Value>.Error.typeMismatch }
        closure(value)
    }
}
// MARK:-

class Node{
    var plugs: [AnyPlug]
    var name: String
    init(_ name:String) {
        self.name = name
        plugs = []
    }

    func addPlug<genPlug:AnyPlug>(_ plug: genPlug){
        // checks the plug name doesn't exist in the list, unique names
        self.validatePlug(plug)

        // Adds plug only if it doesn't exist in the list already
        var found = false
        for inPlg in self.plugs {
            if plug === inPlg {
                found = true
            }
        }
        if !found{
            self.plugs.append(plug)
        }
    }

    func getPlug(name:String) -> [AnyPlug]{
        var matchs: [AnyPlug] = []
        for plg in self.plugs {
            if plg.name == name { matchs.append(plg) }
        }
        return matchs
    }

    public func setPlugName<genPlug:AnyPlug>(plug: genPlug, name:String) -> String{
        var newName = name

        // flag to store if a name collision is found, start true, to initialise the first loop
        var nameCollision = true

        while nameCollision {
            nameCollision = false
            for existing_plg in self.plugs{
                // if the names match, and makes sure one is not comparing the plug to itself.
                if existing_plg.name == newName && plug !== existing_plg {
                    nameCollision = true
                    newName = nameIncrement(name: newName)
                }
            }
        }
        plug.name = newName

        return newName
    }


    /// Checks that the plug is valid. At the moment this means checking no name collisions
    private func validatePlug<genPlug:AnyPlug>(_ plug: genPlug){
        _ = self.setPlugName(plug: plug, name: plug.name)
    }
}

class AddNode<numericType:PlugValue> : Node{
    var acceptedType:numericType.Type
    var out:Plug<numericType>?

    public override required init(_ name:String="AddNode"){
        self.acceptedType = numericType.self
        super.init(name)
        self.initPlugs()
    }

    func initPlugs() {
        let default_value:numericType

        if numericType.self == Double.self{
            default_value = 0.0 as! numericType
        }
        else
            if numericType.self == Float.self{
                default_value = Float(0.0) as! numericType
            }
        else { default_value = 0 as! numericType}

        // Input plugs
        let plg_in_1 = Plug(default_value, name:"value_1" )
        let plg_in_2 = Plug(default_value, name:"value_2" )
        // Output plugs
        let plg_out_1  = Plug(default_value, name:"result")

        plugs.append(plg_in_1)
        plugs.append(plg_in_2)
        self.out = plg_out_1
    }

    func execute() {
        var sum_value:numericType? = nil
        for plg in self.plugs{
            let plgVal = plg as! Plug<numericType>

            if sum_value == nil{
                sum_value = plgVal.value

            }
            else{
                let tValue = plgVal[numericType.self]
                numericType() + acceptedType.init(1.0)
                0.0
                //numericType.Type
                numericType.self
                let ttValue: numericType = plgVal()
                numericType.self
                acceptedType.self
                tValue
                ttValue
                plg[numericType]
                //tValue + 1.0
                //sum_value = sum_value + plgVal.value
                //sum_value =+ 2.0
                //sum_value! + tValue
            }
        }
        self.out!.value = sum_value!
    }

}

/**
 Performs an incrementation on a name

- Parameter name: The string that will have a number appended or incremented on the end of it.
- Returns: An incremented string

 # Example:
~~~
    "name" = "name_1"
    "name_1" = "name_2"
 */
public func nameIncrement(name:String) -> String {
    // check the name has a suffix '_#'
    // if no suffix is found add one, if one is found
    var new_name:String = name

    if name.contains("_"){
        var string_bits = name.split(separator: "_")

        // the last suffix was not a number, so we can just add _1 to the end
        if Int(string_bits.last!) == nil { new_name.append("_1") }
        else
        {
            var index = Int(string_bits.last!) ?? 0
            index += 1
            string_bits.remove(at: string_bits.endIndex-1)
            string_bits.insert(Substring(String(index)), at:string_bits.endIndex)
            new_name = string_bits.joined(separator: "_")
        }
    }
    else {
        // name has no "_", so we append a 1 to the end of the name
        new_name.append("_1")
    }

    return new_name
}

// MARK: IMPLEMENTATION USAGE

var newNode = Node("Dynamic Node")

var plg_1 = Plug(1, name:"index")
var plg_2 = Plug([2.2, 45.0, 90.1], name:"angles")
var plg_3 = Plug([0.0, 0.0, 0.0], name:"angles")  // Name conflict

// Add plug to Node
newNode.addPlug(plg_1)
newNode.addPlug(plg_2)
newNode.addPlug(plg_3)

var tempPlug_1 = newNode.plugs[0]
var tempPlug_2 = newNode.plugs[2]
// How would I cast the AnyNode back into a Plug<Type>

// Is there a more straight forward way to do this, which doesn't require the creation of a new variable, and knowing the type
// Would it be possible to store the type of plug in the protocol and leverage that to cast back into a plug
// eg. var tp = tempPlug_1 as! Plug<Int>

// is there a way to hold a type in a variable so you can use it later to cast the generic type
/*
 var magicType = Int // have it as a variable in the protocol?
 var tp = tempPlug_1 as! Plug<magicType>
 */

var tp = tempPlug_1 as! Plug<Int>

tp === plg_1 // is true, so they are pointers to the same location, even though its a new variable and been cast
tp.value = 5
var sds = newNode.plugs[0][Int]
newNode.plugs[0].anyValue = 10

var sds2 = newNode.plugs[0]
plg_1.value = 20    // best case scenario



// testing purposes to change type easier for testing
typealias caster = Double

let addNode = AddNode<caster>("newNode")
addNode.name = "Add Node"
addNode.plugs[0].anyValue = caster(2.0)
addNode.plugs[1].anyValue = caster(4.0)
addNode.execute()
addNode.out

// instantiate a variable from the node type // investigation purposes
var testType = addNode.acceptedType.init(2)
testType + 2

Thanks again for taking a look,

Regards, Simon

来源:https://stackoverflow.com/questions/61675702/arithmetic-with-generics-and-protocols

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!