Can I simulate traits/mixins in Swift?

梦想与她 提交于 2019-12-03 05:39:40

问题


Does Swift have a way of mixing in traits, a la Scala? The section of the Swift book on using extensions to add protocols to existing classes comes tantalizingly close. However, since protocols can't contain an implementation, this can't be used to mix code into a class. Is there another way?


回答1:


As of Swift 2.0, yes!

Providing Default Implementations

You can use protocol extensions to provide a default implementation to any method or property requirement of that protocol. If a conforming type provides its own implementation of a required method or property, that implementation will be used instead of the one provided by the extension.




回答2:


One way to simulate mixing is use generic function to provide implementation

For example with these protocols

protocol Named {
    func GetName() -> String
}

protocol NamedExtension {
    func GetLowercaseName() -> String
    func GetUppercaseName() -> String
}

I want some class to implement GetName() and use mixing so they also get GetLowercaseName() and GetUppercaseName() without implement them

This is the implementation of NamedExtension as in free function

func GetLowercaseNameImpl<T:Named>(obj:T) -> String {
    return obj.GetName().lowercaseString
}

func GetUppercaseNameImpl<T:Named>(obj:T) -> String {
    return obj.GetName().uppercaseString
}

and extensions on Int

extension Int : Named {
    func GetName() -> String {
        return "Int"
    }
}

extension Int : NamedExtension {
    // use provided implementation
    func GetLowercaseName() -> String {
        return GetLowercaseNameImpl(self)
    }
    func GetUppercaseName() -> String {
        return GetUppercaseNameImpl(self)
    }
}

and I can use

1.GetName() // result Int
1.GetUppercaseName() // result "INT"
1.GetLowercaseName() // result "int"



回答3:


I don't know Scala, but from what you're telling me it is possible to simultaneously create a protocol and an extension that extends a type to add "pseudo-trait" behavior.

For example:

protocol IsGreaterThan
{
    func isGreaterThan(other:Int) -> Bool
    func isNotGreaterThan(other:Int) -> Bool
}

extension Int : IsGreaterThan
{
    func isGreaterThan(other:Int) -> Bool
    {
        return self > other
    }

    func isNotGreaterThan(other:Int) -> Bool
    {
        return !isGreaterThan(other)
    }
}

The real hamstring is how generics are somewhat limited for now. I think they will improve a lot in the coming revisions of Swift.




回答4:


Similar to Bryan Chen's answer:

import Foundation

protocol Named {
    var name : String { get }
}

protocol NamedExtension : Named { // NB extends Named
    var lowercaseName : String { get }
    var uppercaseName : String { get }
}

struct NamedExtensionDefault { // Put defaults inside a struct to keep name spaces seperate
    static func lowercaseName(named : NamedExtension) -> String {
        return (named.name as NSString).lowercaseString
    }
    static func uppercaseName(named : NamedExtension) -> String {
        return (named.name as NSString).uppercaseString
    }
}

extension Int : NamedExtension {
    var name : String {
    return "Int"
    }
    // Use default implementation
    var lowercaseName : String {
    return NamedExtensionDefault.lowercaseName(self)
    }
    var uppercaseName : String {
    return NamedExtensionDefault.uppercaseName(self)
    }
}

1.name // result Int
1.uppercaseName // result "INT"
1.lowercaseName // result "int"

The main difference from Bryan's answer is that I didn't use generics because I made NamedExtension extends Named, so that the default implementations can access name.




回答5:


Here's my (not yet widely tested) way of doing what I think are Scala traits in Swift 2.1.1, Playgrounds-ready, two versions:

Less flexible:

protocol BigBadProtocol {
    func madFunc() -> String;
    // func otherFunc();
    // Maybe a couple more functions here.
}

protocol BlueMadFuncUser: BigBadProtocol {}

extension BlueMadFuncUser {
    func madFunc() -> String {
        return "Blue"
    }
}

protocol RedMadFuncUser: BigBadProtocol {}

extension RedMadFuncUser {
    func madFunc() -> String {
        return "Red"
    }
}

class ClearClass: BigBadProtocol {
    func madFunc() -> String {
        return "Clear"
    }
}

class BlueClass: BlueMadFuncUser {}

class RedClass: RedMadFuncUser {}

More flexible:

protocol BigBadProtocol {
    func madFunc() -> String;
    // func otherFunc();
    // Maybe a couple more functions here.
}

protocol BlueMadFuncUser {}

extension BigBadProtocol where Self: BlueMadFuncUser {
    func madFunc() -> String {
        return "Blue"
    }
}

protocol RedMadFuncUser {}

extension BigBadProtocol where Self: RedMadFuncUser {
    func madFunc() -> String {
        return "Red"
    }
}

class ClearClass: BigBadProtocol {
    func madFunc() -> String {
        return "Clear"
    }
}

class BlueClass: BigBadProtocol, BlueMadFuncUser {}

class RedClass: BigBadProtocol, RedMadFuncUser {}

Sanity check:

var classes: [BigBadProtocol] = [ClearClass(), BlueClass(), RedClass()]

// Prints "Clear, Blue, Red\n"
print((classes.map { $0.madFunc() }).joinWithSeparator(", "))

// Print another way for Playgrounds, which appears to bug out on the lines above
var s = ""
for klass in classes {
    s += klass.madFunc() + " "
}
print(s)

BlueMadFuncUser and RedMadFuncUser are two versions of a trait. My terminology might be off, but then you can independently create a second trait like that and mix and match in your classes as you please.

Would be much more challenging or boiler-plate-y to reuse logic like that with an inheritance-based approach.

I ended up wanting this pattern after finding it very useful in Hack for PHP, where from what I can tell traits are very similar to Scala's: https://docs.hhvm.com/hack/other-features/trait-and-interface-requirements)



来源:https://stackoverflow.com/questions/24107693/can-i-simulate-traits-mixins-in-swift

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