How to adhere to protocol method with a Raw type in method argument?

纵然是瞬间 提交于 2020-01-05 18:54:43

问题


protocol Measurement {
     mutating func convert(#toUnit: String)
}

enum MassUnit : String {
    case Milligram = "mg"
}

enum VolumeUnit : String {
     case Milliliter = "ml"
}

struct Mass : Measurement {
     mutating func convert(#toUnit: MassUnit)
     // Build error: Does not adhere to 'Measurement'
}

struct Volume : Measurement {
     mutating func convert(#toUnit: VolumeUnit)
     // Build error: Does not adhere to 'Measurement'
}

func +<T: Measurement> (left:T, right:T) -> Measurement {
    let newRightValue  = right.convert(toUnit: left.unit)
    return T(quantity: left.quantity + newRightValue.quantity , unit: left.unit)
}

How can I make Mass adhere correctly to Measurement? What change in the Measurement protocol is needed to get it to work with enum of type String?

Question updated with more information about why the convert method signature should say something about the argument given. The code is part of an open source Unit framework i'm building called Indus Valley


回答1:


When dealing with your unit conversions, I advise not trying to use Strings to represent the units when converting the way you've got setup above. That's going to make your code complicated with checking the String can be converted to its respective enum every time you want to make a conversion. Also, what if you want to use the a MassUnit/VolumeUnit instead of a String?

I would recommend using the a setup similar to what I've outlined below. It references my previous answer - How to represent magnitude for mass in Swift?

(Note - I've excluded anything to do with the volume because it's basically the same as the implementation for the mass)

I'd make the units like so:

protocol UnitProtocol {
    var magnitude: Int { get }

    init?(rawValue: String)
}

// Taken from my previous answer.
enum MassUnit: String, UnitProtocol, Printable {
    case Milligram = "mg"
    case Gram      = "g"

    var magnitude: Int {
        let mag: Int

        switch self {
            case .Milligram: mag = -3
            case .Gram     : mag =  0
        }

        return mag
    }

    var description: String {
        return rawValue
    }
}

// Not making this a method requirement of `UnitProtocol` means you've only got to 
// write the code once, here, instead of in every enum that conforms to `UnitProtocol`.
func ordersOfMagnitudeFrom<T: UnitProtocol>(unit1: T, to unit2: T) -> Int {
    return unit1.magnitude - unit2.magnitude
}

Then I'd make the masses/volumes like so:

protocol UnitConstruct {
    typealias UnitType: UnitProtocol
    var amount: Double   { get }
    var unit  : UnitType { get }

    init(amount: Double, unit: UnitType)
}

struct Mass : UnitConstruct {
    let amount: Double
    let unit  : MassUnit
}

Now for the converting function! Using a global function means you don't need to rewrite the code for every type than conforms to UnitConstruct.

func convert<T: UnitConstruct>(lhs: T, toUnits unit: T.UnitType) -> T {
    let x = Double(ordersOfMagnitudeFrom(lhs.unit, to: unit))
    return T(amount: lhs.amount * pow(10, x), unit: unit)
}

// This function is for converting to different units using a `String`,
// as asked in the OP.
func convert<T: UnitConstruct>(lhs: T, toUnits unit: String) -> T? {
    if let unit = T.UnitType(rawValue: unit) {
        return convert(lhs, toUnits: unit)
    }

    return nil
}

You can then use the previous code like so:

let mass1 = Mass(amount: 1.0, unit: .Gram)
let mass2 = convert(mass1, toUnits: .Milligram) // 1000.0 mg

// Or, converting using Strings:
let right = convert(mass1, toUnits: "mg")       // Optional(1000.0 mg)
let wrong = convert(mass1, toUnits: "NotAUnit") // nil



回答2:


You probably confuse enum MassUnit : String with inheritance.

Contrary to class ChildClass : ParentClass which denotes that ChildClass inherits from ParentClass, enum MassUnit : String has a slightly different meaning, telling that the rawType of the enum is String, not that the enum inherits the String type.

So MassUnit is not of type String. You MassUnit's rawValues are of type String, but to access that you need to call the rawValue property of the enum to get that String equivalent.

Therefore, mutating func convert(#toUnit: String) and mutating func convert(#toUnit: MassType) are not compatible, as MassType is not a String itself. Only its rawValue is.



来源:https://stackoverflow.com/questions/30109996/how-to-adhere-to-protocol-method-with-a-raw-type-in-method-argument

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