Can I simulate traits/mixins in Swift?

前端 未结 5 647
南旧
南旧 2021-02-01 16:45

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,

相关标签:
5条回答
  • 2021-02-01 17:06

    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.

    0 讨论(0)
  • 2021-02-01 17:15

    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.

    0 讨论(0)
  • 2021-02-01 17:20

    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)

    0 讨论(0)
  • 2021-02-01 17:23

    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.

    0 讨论(0)
  • 2021-02-01 17:26

    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"
    
    0 讨论(0)
提交回复
热议问题