Swiftwater Logo

Swift Generics -Part Deux: Associated Type Protocols

This entry is part 12 of 21 in the series Swiftwater

ABSTRACT

In the last post, we discussed the basics of Swift generics, and how they are pretty much built into the language. One way this is demonstrated, is by the way that Swift implements generic (associated type) protocols. In this post, we’ll explore this a bit further.

THE BASIC DEFINITION

Instead of using the classic “<T>” syntax, protocols define an “associatedtype” keyword. This keyword replaces the “typealias” keyword in older versions of Swift:

protocol GenericBaseProtocol {
    associatedtype T
    
    var myProperty: T {get set}
    init(_ myProperty: T )
}

The above makes the protocol type generic. It will require you to declare a typealias, in order to define T:

// You can declare both Comparable and non-Comparable types with this protocol.
// This is Comparable
struct GenStructB: GenericBaseProtocol {
    typealias T = Int
    var myProperty: T = 0
    init(_ myProperty: T ) {
        self.myProperty = myProperty
    }
}

// This is non-Comparable
class GenClassA: GenericBaseProtocol {
    typealias T = [String:String]
    var myProperty: T = [:]
    required init(_ myProperty: T ) {
        self.myProperty = myProperty
    }
}

Note that the above does not make the protocol, itself, generic. It makes internal data types generic, and requires that these data types be specifically assigned at the time classes (or structs) are defined from the protocol.

MULTIPLE ASSOCIATED TYPES

You can have multiple associated types:

protocol GenericBaseProtocolWithTwoTypes {
    associatedtype T
    associatedtype S
    
    var myProperty1: T {get set}
    var myProperty2: S {get set}
    
    init(myProperty1: T, myProperty2: S)
}

Which requires you to define and implement BOTH types:

struct GenericTwoTypesStruct: GenericBaseProtocolWithTwoTypes {
    typealias T = Int
    typealias S = [String:String]
    
    var myProperty1: T
    var myProperty2: S
    
    init(myProperty1: T, myProperty2: S) {
        self.myProperty1 = myProperty1
        self.myProperty2 = myProperty2
    }
}

class GenericTwoTypesClass: GenericBaseProtocolWithTwoTypes {
    typealias T = Int
    typealias S = [String:String]
    
    var myProperty1: T
    var myProperty2: S
    
    required init(myProperty1: T, myProperty2: S) {
        self.myProperty1 = myProperty1
        self.myProperty2 = myProperty2
    }
}

This will not work (Trying to define and use only one of the associated types):

class GenericTwoTypes: GenericBaseProtocolWithTwoTypes {
    typealias T = Int

    var myProperty1: T

    required init(myProperty1: T) {
        self.myProperty1 = myProperty1
    }
}

INTO ACTION

Now, let’s look at how we can implement and specialize the generics.

Let’s begin by saying we’d like our protocol to act as an “Equatable” or “Comparable” protocol. These allow instances to be compared for equality and/or precedence.

There’s a couple of ways that we can do that:

Declaring the Protocol to be Comparable

In this method, we actually declare the protocol as an extension of the Comparable Swift Behavior protocol:

protocol GenericBaseProtocolBothCompType: Comparable {
    associatedtype T: Comparable
    
    var myProperty: T {get set}
    init(_ myProperty: T )
}

Note that both the protocol and the associated type are defined to be Comparable.

If we do this, then we’ll need to make sure that we implement at least the required Equatable method:

static func == (lhs: Self, rhs: Self) -> Bool

We are also required to specify one of the Comparable required static funtions:

static func < (lhs: Self, rhs: Self) -> Bool

Like so:

// This extension satisfies the Equatable protocol.
extension GenericBaseProtocolBothCompType {
    static func ==(lhs: Self, rhs: Self) -> Bool {
        return lhs.myProperty == rhs.myProperty
    }
}

// This extension satisfies the Comparable protocol.
extension GenericBaseProtocolBothCompType {
    static func <(lhs: Self, rhs: Self) -> Bool {
        return lhs.myProperty < rhs.myProperty
    }
}

The associated type needs to be Comparable, because that allows these functions to work. If it is not restricted to Comparable, then you’ll get syntax errors when trying to define them.

Note that we are using protocol extensions to define these functions as default implementations.

Adding Conditional Protocol Extension Functions

The other method allows us to extend the entirely generic protocol that we defined initially. We do this by making the implementation of the functions, themselves, to be dependent on whether or not the type T is Comparable:

// This extension uses the Equatable protocol (Comparable extends Equatable). Note the capitalized "Self".
extension GenericBaseProtocol where T: Equatable {
    static func ==(lhs: Self, rhs: Self) -> Bool {
        return lhs.myProperty == rhs.myProperty
    }
}

// This extension uses the Comparable protocol.
extension GenericBaseProtocol where T: Comparable {
    static func <(lhs: Self, rhs: Self) -> Bool {
        return lhs.myProperty < rhs.myProperty
    }
    static func >(lhs: Self, rhs: Self) -> Bool {
        return lhs.myProperty > rhs.myProperty
    }
}

In the above case, we have extended the entirely generic protocol. However, these extensions will ONLY be available if type T is Equatable and/or Comparable. Let’s prove this.

First, we’ll declare a class that uses Int (Int is a Comparable type):

// Test the Comparable behavior
let lhs2 = GenClassB3(3)
let rhs2 = GenClassB3(4)

let leftEqualToRight2 = lhs2 == rhs2
let leftGreaterThanRight2 = lhs2 > rhs2
let leftLessThanRight2 = lhs2 < rhs2

let rightEqualToLeft2 = rhs2 == lhs2
let rightGreaterThanLeft2 = rhs2 > lhs2
let rightLessThanLeft2 = rhs2 < lhs2

let leftEqualToLeft2 = lhs2 == GenClassB3(3)
let rightEqualToRight2 = lhs2 == GenClassB3(4)

That all works as expected.

Now, lets use an Array of String ([String] is not directly Equatable):

// Here, we define a type that is not Comparable.
class GenClassB3A: GenericBaseProtocol {
    typealias T = [String]
    var myProperty: T = []
    
    required init(_ myProperty: T ) {
        self.myProperty = myProperty
    }
}

let lhs2A = GenClassB3A(["HI"])
let rhs2A = GenClassB3A(["Howaya"])

None of this will work, because the class that was created does not have an Equatable type:

let leftEqualToRight2A = lhs2A == rhs2A
let leftEqualToRight2A = lhs2A == rhs2A
let leftGreaterThanRight2A = lhs2A > rhs2A
let leftLessThanRight2A = lhs2A < rhs2A

let rightEqualToLeft2A = rhs2A == lhs2A
let rightGreaterThanLeft2A = rhs2A > lhs2A
let rightLessThanLeft2A = rhs2A < lhs2A

let leftEqualToLeft2A = lhs2A == GenClassB3A(["HI"])
let rightEqualToRight2A = lhs2A == GenClassB3A(["Howaya"])

However…

As I demonstrate in the sample playground below, the “parameterized” extensions (generally using the where clause) act like high-specificity CSS. In other words, if an extension is more specific than another one, it is used instead of the less-specific one. In particular, here:

// This is the default extension, implementing non-functional stubs.
extension GenericBaseProtocol {
    static func ==(lhs: Self, rhs: Self) -> Bool {
        return false
    }
    
    static func <(lhs: Self, rhs: Self) -> Bool {
        return false
    }
    
    static func >(lhs: Self, rhs: Self) -> Bool {
        return false
    }

    var isEquatable:Bool { return false }
    var isComparable:Bool { return false }
}

The above is the least-specific or default, extension. It is used when there are no more specific versions. We take advantage of this to return a false for a conformance test, and give non-functional operator stubs.

Below, we declare a couple of more specific extensions. If the associated type is Equatable, then the first extension below overrides the ==() operator and isEquatable calculated property.

If the type is Comparable, then all of the operators are overridden, and the isComparable calculated property returns true.

// This extension is used when the associated type conforms to the Equatable protocol.
extension GenericBaseProtocol where T: Equatable {
    static func ==(lhs: Self, rhs: Self) -> Bool {
        return lhs.myProperty == rhs.myProperty
    }
    
    var isEquatable:Bool { return true }
}

// This extension is used when the associated type conforms to the Comparable protocol.
extension GenericBaseProtocol where T: Comparable {
    static func <(lhs: Self, rhs: Self) -> Bool {
        return lhs.myProperty < rhs.myProperty
    }

    static func >(lhs: Self, rhs: Self) -> Bool {
        return lhs.myProperty > rhs.myProperty
    }
    
    var isComparable:Bool { return true }
}

As you can see, if you run the playground, you don’t have to split up the “generic” playground. Like CSS, only the part that is less specific is overridden. The stuff that is still the most specific (even though it is the default “general” extension) is still applied.

Let’s explore this a bit more closely.

Here, we define an instance of a class with a Comparable data type (Int):

// Here, we define a Comparable type
class GenClassB3: GenericBaseProtocol {
    typealias T = Int
    var myProperty: T = 0
    
    required init(_ myProperty: T ) {
        self.myProperty = myProperty
    }
}

This will cause the two extensions (both of them) to override the default extension:

// This is the default extension, implementing non-functional stubs. extension GenericBaseProtocol { static func ==(lhs: Self, rhs: Self) -> Bool { return false } static func <(lhs: Self, rhs: Self) -> Bool { return false } static func >(lhs: Self, rhs: Self) -> Bool { return false } var isEquatable:Bool { return false } var isComparable:Bool { return false } }
// This extension uses the Equatable protocol (Comparable extends Equatable). Note the capitalized "Self". // If the class is Equatable, then we return a true for isEquatable. // This extension is used when the associated type conforms to the Equatable protocol. extension GenericBaseProtocol where T: Equatable { static func ==(lhs: Self, rhs: Self) -> Bool { return lhs.myProperty == rhs.myProperty } var isEquatable:Bool { return true } } // This extension is used when the associated type conforms to the Comparable protocol. extension GenericBaseProtocol where T: Comparable { static func <(lhs: Self, rhs: Self) -> Bool { return lhs.myProperty < rhs.myProperty } static func >(lhs: Self, rhs: Self) -> Bool { return lhs.myProperty > rhs.myProperty } var isComparable:Bool { return true } }

Next, we’ll use a data type that is Equatable, but not Comparable (Bool). This will cause only the Equatable-constrained extension to take effect:

// Here, we define an Equatable type
class GenClassB4: GenericBaseProtocol {
    typealias T = Bool
    var myProperty: T = false
    
    required init(_ myProperty: T ) {
        self.myProperty = myProperty
    }
}
// This is the default extension, implementing non-functional stubs.
extension GenericBaseProtocol {
    static func ==(lhs: Self, rhs: Self) -> Bool {
        return false
    }

    static func <(lhs: Self, rhs: Self) -> Bool {
        return false
    }
    
    static func >(lhs: Self, rhs: Self) -> Bool {
        return false
    }

    var isEquatable:Bool { return false }
    var isComparable:Bool { return false }
}


// This extension uses the Equatable protocol (Comparable extends Equatable). Note the capitalized "Self". // If the class is Equatable, then we return a true for isEquatable. // This extension is used when the associated type conforms to the Equatable protocol. extension GenericBaseProtocol where T: Equatable { static func ==(lhs: Self, rhs: Self) -> Bool { return lhs.myProperty == rhs.myProperty } var isEquatable:Bool { return true } }
// This extension is used when the associated type conforms to the Comparable protocol. extension GenericBaseProtocol where T: Comparable { static func <(lhs: Self, rhs: Self) -> Bool { return lhs.myProperty < rhs.myProperty } static func >(lhs: Self, rhs: Self) -> Bool { return lhs.myProperty > rhs.myProperty } var isComparable:Bool { return true } }

Finally, we’ll define a type that is neither Equatable, nor Comparable (An Array of String). This will cause the protocol to fall back to its default implementation:

// Here, we define a type that is neither Equatable, nor Comparable
class GenClassB5: GenericBaseProtocol {
    typealias T = [String]
    var myProperty: T = []
    
    required init(_ myProperty: T ) {
        self.myProperty = myProperty
    }
}






// This is the default extension, implementing non-functional stubs. extension GenericBaseProtocol { static func ==(lhs: Self, rhs: Self) -> Bool { return false } static func <(lhs: Self, rhs: Self) -> Bool { return false } static func >(lhs: Self, rhs: Self) -> Bool { return false } var isEquatable:Bool { return false } var isComparable:Bool { return false } }
// This extension uses the Equatable protocol (Comparable extends Equatable). Note the capitalized "Self". // If the class is Equatable, then we return a true for isEquatable. // This extension is used when the associated type conforms to the Equatable protocol. extension GenericBaseProtocol where T: Equatable { static func ==(lhs: Self, rhs: Self) -> Bool { return lhs.myProperty == rhs.myProperty } var isEquatable:Bool { return true } } // This extension is used when the associated type conforms to the Comparable protocol. extension GenericBaseProtocol where T: Comparable { static func <(lhs: Self, rhs: Self) -> Bool { return lhs.myProperty < rhs.myProperty } static func >(lhs: Self, rhs: Self) -> Bool { return lhs.myProperty > rhs.myProperty } var isComparable:Bool { return true } }

CONCLUSION

Swift has a really nice implementation of generic programming. It has generics built into the fundamental language, itself, and its use of associated types in protocols is especially effective.

On this page, I just briefly touched on the possibilities of the use of generics in associated types, and demonstrated the way the where clause can be applied to give you “smart protocols,” based on type constraints.


SAMPLE PLAYGROUND

// This is a completely generic protocol. You can use any type for "T".
protocol GenericBaseProtocol {
    associatedtype T
    
    var myProperty: T {get set}
    init(_ myProperty: T )
}

// You can declare both Comparable and non-Comparable types with this protocol.
// This is Comparable
class GenClassB: GenericBaseProtocol {
    typealias T = Int
    var myProperty: T = 0
    // When you conform to a protocol with an init(), you need to add the "required" keyword to your implementation.
    required init(_ myProperty: T ) {
        self.myProperty = myProperty
    }
}

// This is non-Comparable
class GenClassA: GenericBaseProtocol {
    typealias T = [String:String]
    var myProperty: T = [:]
    required init(_ myProperty: T ) {
        self.myProperty = myProperty
    }
}

// This will not work. You cannot redefine associated types in a protocol extension.
//extension GenericBaseProtocol {
//    associatedtype T: Comparable
//
//    var myProperty: T {get set}
//}

// This will not work. You cannot add an associatedType via an extension.
//extension GenericBaseProtocol {
//    associatedtype S
//}

// Now, here we will add conditional extensions to the original, generic protocol.
// These allow classes based on this protocol to act as Equatable and Comparable, if the data type is Comparable,
// or just Equatable, if the data type is Equatable, but not Comparable.

// This extension is for when the class is not Equatable.
// The == always returns false, and we have an isEquatable Bool that tells us the class is not Equatable.
// Thanks to Alain T. for his guidance: https://stackoverflow.com/a/48711730/879365
// This is the default extension, implementing non-functional stubs.
extension GenericBaseProtocol {
    static func ==(lhs: Self, rhs: Self) -> Bool {
        return false
    }
    
    static func <(lhs: Self, rhs: Self) -> Bool {
        return false
    }
    
    static func >(lhs: Self, rhs: Self) -> Bool {
        return false
    }

    var isEquatable:Bool { return false }
    var isComparable:Bool { return false }
}

// This extension uses the Equatable protocol (Comparable extends Equatable). Note the capitalized "Self".
// If the class is Equatable, then we return a true for isEquatable.
// This extension is used when the associated type conforms to the Equatable protocol.
extension GenericBaseProtocol where T: Equatable {
    static func ==(lhs: Self, rhs: Self) -> Bool {
        return lhs.myProperty == rhs.myProperty
    }
    
    var isEquatable:Bool { return true }
}

// This extension is used when the associated type conforms to the Comparable protocol.
extension GenericBaseProtocol where T: Comparable {
    static func <(lhs: Self, rhs: Self) -> Bool {
        return lhs.myProperty < rhs.myProperty
    }
    static func >(lhs: Self, rhs: Self) -> Bool {
        return lhs.myProperty > rhs.myProperty
    }
    
    var isComparable:Bool { return true }
}

// Here, we define a Comparable type
class GenClassB3: GenericBaseProtocol {
    typealias T = Int
    var myProperty: T = 0
    
    required init(_ myProperty: T ) {
        self.myProperty = myProperty
    }
}

// Test the Comparable behavior
let lhs2 = GenClassB3(3)
let rhs2 = GenClassB3(4)

print ( "lhs2 is" + (lhs2.isEquatable ? "" : " not") + " Equatable." )
print ( "lhs2 is" + (lhs2.isComparable ? "" : " not") + " Comparable." )

print ( "rhs2 is" + (rhs2.isEquatable ? "" : " not") + " Equatable." )
print ( "rhs2 is" + (rhs2.isComparable ? "" : " not") + " Comparable." )

let leftEqualToRight2 = lhs2 == rhs2
let leftGreaterThanRight2 = lhs2 > rhs2
let leftLessThanRight2 = lhs2 < rhs2

let rightEqualToLeft2 = rhs2 == lhs2
let rightGreaterThanLeft2 = rhs2 > lhs2
let rightLessThanLeft2 = rhs2 < lhs2

let leftEqualToLeft2 = lhs2 == GenClassB3(3)
let rightEqualToRight2 = lhs2 == GenClassB3(4)

// Here, we define a type that is not Comparable.
class GenClassB3A: GenericBaseProtocol {
    typealias T = [String]
    var myProperty: T = []
    
    required init(_ myProperty: T ) {
        self.myProperty = myProperty
    }
}

let lhs2A = GenClassB3A(["HI"])
let rhs2A = GenClassB3A(["Howaya"])

print ( "lhs2A is" + (lhs2A.isEquatable ? "" : " not") + " Equatable." )
print ( "lhs2A is" + (lhs2A.isComparable ? "" : " not") + " Comparable." )

print ( "rhs2A is" + (rhs2A.isEquatable ? "" : " not") + " Equatable." )
print ( "rhs2A is" + (rhs2A.isComparable ? "" : " not") + " Comparable." )

// Because of the game we played up there, these will compile, but the comparisons will always fail.
let leftEqualToRight2A = lhs2A == rhs2A
let leftGreaterThanRight2A = lhs2A > rhs2A
let leftLessThanRight2A = lhs2A < rhs2A

let rightEqualToLeft2A = rhs2A == lhs2A
let rightGreaterThanLeft2A = rhs2A > lhs2A
let rightLessThanLeft2A = rhs2A < lhs2A

let leftEqualToLeft2A = lhs2A == GenClassB3A(["HI"])
let rightEqualToRight2A = lhs2A == GenClassB3A(["Howaya"])

// Here, we define an Equatable (but not Comparable) type
class GenClassB4: GenericBaseProtocol {
    typealias T = Bool
    var myProperty: T = false
    
    required init(_ myProperty: T ) {
        self.myProperty = myProperty
    }
}

let lhs2B = GenClassB4(true)
let rhs2B = GenClassB4(true)

print ( "lhs2B is" + (lhs2B.isEquatable ? "" : " not") + " Equatable." )
print ( "lhs2B is" + (lhs2B.isComparable ? "" : " not") + " Comparable." )

print ( "rhs2B is" + (rhs2B.isEquatable ? "" : " not") + " Equatable." )
print ( "rhs2B is" + (rhs2B.isComparable ? "" : " not") + " Comparable." )

let leftEqualToRight2B = lhs2B == rhs2B
let leftGreaterThanRight2B = lhs2B > rhs2B
let leftLessThanRight2B = lhs2B < rhs2B

let rightEqualToLeft2B = rhs2B == lhs2B
let rightGreaterThanLeft2B = rhs2B > lhs2B
let rightLessThanLeft2B = rhs2B < lhs2B

let leftEqualToLeft2B = lhs2B == GenClassB4(true)
let rightEqualToRight2B = lhs2B == GenClassB4(false)

// This defines a new protocol, based on Comparable.
protocol GenericBaseProtocolBothCompType: Comparable {
    associatedtype T: Comparable
    
    var myProperty: T {get set}
    init(_ myProperty: T )
}

// You cannot define a protocol extension initializer.
//extension GenericBaseProtocolBothCompType {
//    init(_ myProperty: T ) {
//            self.myProperty = myProperty
//    }
//}

// This extension satisfies the Equatable protocol.
extension GenericBaseProtocolBothCompType {
    static func ==(lhs: Self, rhs: Self) -> Bool {
        return lhs.myProperty == rhs.myProperty
    }
}

// This extension satisfies the Comparable protocol.
extension GenericBaseProtocolBothCompType {
    static func <(lhs: Self, rhs: Self) -> Bool {
        return lhs.myProperty < rhs.myProperty
    }
}

// This is an error. It will not work, because Dictionary is not Comparable.
//class GenClassA2: GenericBaseProtocolBothCompType {
//    typealias T = [String:String]
//    var myProperty: T = [:]
//}

class GenClassB2: GenericBaseProtocolBothCompType {
    typealias T = Int
    var myProperty: T = 0
    
    required init(_ myProperty: T ) {
        self.myProperty = myProperty
    }
}

let lhs = GenClassB2(3)
let rhs = GenClassB2(4)

let leftEqualToRight = lhs == rhs
let leftGreaterThanRight = lhs > rhs
let leftLessThanRight = lhs < rhs

let rightEqualToLeft = rhs == lhs
let rightGreaterThanLeft = rhs > lhs
let rightLessThanLeft = rhs < lhs

let leftEqualToLeft = lhs == GenClassB2(3)
let rightEqualToRight = lhs == GenClassB2(4)

// This defines a new protocol, based on Comparable, and leaves the associated type as a free agent.
protocol GenericBaseProtocolBothCompType2: Comparable {
    associatedtype T
    
    var myProperty: T {get set}
    init(_ myProperty: T )
}

// However, these won't work, as the data type is not Comparable.
//// This extension satisfies the Equatable protocol.
//extension GenericBaseProtocolBothCompType2 {
//    static func ==(lhs: Self, rhs: Self) -> Bool {
//        return lhs.myProperty == rhs.myProperty
//    }
//}
//
//// This extension satisfies the Comparable protocol.
//extension GenericBaseProtocolBothCompType2 {
//    static func <(lhs: Self, rhs: Self) -> Bool {
//        return lhs.myProperty < rhs.myProperty
//    }
//}

// These will work, because you are specifying that the type needs to be Equatable/Comparable.
// This extension satisfies the Equatable protocol.
extension GenericBaseProtocolBothCompType2 where T: Equatable {
    static func ==(lhs: Self, rhs: Self) -> Bool {
        return lhs.myProperty == rhs.myProperty
    }
}

// This extension satisfies the Comparable protocol.
extension GenericBaseProtocolBothCompType2 where T: Comparable {
    static func <(lhs: Self, rhs: Self) -> Bool {
        return lhs.myProperty < rhs.myProperty
    }
}

class GenericClassFromType2: GenericBaseProtocolBothCompType2 {
    typealias T = Int
    var myProperty: T = 0
    
    required init(_ myProperty: T ) {
        self.myProperty = myProperty
    }
}

let lhs4 = GenericClassFromType2(3)
let rhs4 = GenericClassFromType2(4)

let leftEqualToRigh4t = lhs4 == rhs4
let leftGreaterThanRight4 = lhs4 > rhs4
let leftLessThanRight4 = lhs4 < rhs4

let rightEqualToLeft4 = rhs4 == lhs4
let rightGreaterThanLeft4 = rhs4 > lhs4
let rightLessThanLeft4 = rhs4 < lhs4

let leftEqualToLeft4 = lhs4 == GenericClassFromType2(3)
let rightEqualToRight4 = lhs4 == GenericClassFromType2(4)

// This protocol has two associated types.
protocol GenericBaseProtocolWithTwoTypes {
    associatedtype T
    associatedtype S
    
    var myProperty1: T {get set}
    var myProperty2: S {get set}
    
    init(myProperty1: T, myProperty2: S)
}

// This will not work. You need to define and implement BOTH types.
//class GenericTwoTypes: GenericBaseProtocolWithTwoTypes {
//    typealias T = Int
//
//    var myProperty1: T
//
//    required init(myProperty1: T) {
//        self.myProperty1 = myProperty1
//    }
//}

struct GenericTwoTypesStruct: GenericBaseProtocolWithTwoTypes {
    typealias T = Int
    typealias S = [String:String]
    
    var myProperty1: T
    var myProperty2: S
    
    // You do not need the "required" keyword when implementing a struct from the protocol.
    init(myProperty1: T, myProperty2: S) {
        self.myProperty1 = myProperty1
        self.myProperty2 = myProperty2
    }
}

class GenericTwoTypesClass: GenericBaseProtocolWithTwoTypes {
    typealias T = Int
    typealias S = [String:String]
    
    var myProperty1: T
    var myProperty2: S
    
    required init(myProperty1: T, myProperty2: S) {
        self.myProperty1 = myProperty1
        self.myProperty2 = myProperty2
    }
}