Workaround for generics not currently supporting conditional conformance to a protocol


(Howard Lovatt) #1

Hi All,

Does anyone have a good workaround for generics not currently supporting
conditional conformance to a protocol. As stated in the Generics Manifesto
something like this would be nice:

  extension Array: Equatable where Element: Equatable {
        static func ==(lhs: Array, rhs: Array) -> Bool { ... }
    }

But I would currently write a wrapper, something like:

    struct ArrayE<T: Equatable> {
        var elements: [T]
    }
    extension ArrayE: Equatable {
        static func ==(lhs: ArrayE, rhs: ArrayE) -> Bool { ... }
    }

This can get unwieldy when there are a lot of conditional protocol
extensions required, i.e. wrappers round wrappers.

Is there a better way?

Thanks for any tips,

  -- Howard.


(Tim Vermeulen) #2

Not really, unfortunately. The standard library is littered with types because of exactly this (e.g. `MutableRangeReplaceableRandomAccessSlice`).

Conditional conformance can’t come fast enough!

···

Hi All,

Does anyone have a good workaround for generics not currently supporting conditional conformance to a protocol. As stated in the Generics Manifesto something like this would be nice:

extension Array: Equatable where Element: Equatable {
static func ==(lhs: Array, rhs: Array) ->Bool { ... }
}

But I would currently write a wrapper, something like:

struct ArrayE<T: Equatable>{
var elements: [T]
}
extension ArrayE: Equatable {
static func ==(lhs: ArrayE, rhs: ArrayE) ->Bool { ...}
}

This can get unwieldy when there are a lot of conditional protocol extensions required, i.e. wrappers round wrappers.

Is there a better way?

Thanks for any tips,

-- Howard._______________________________________________
swift-users mailing list
swift-users@swift.org
https://lists.swift.org/mailman/listinfo/swift-users


(David Sweeris) #3

You are correct. The work-around is to use two extensions and overload the == operator:

extension Array: Equatable {
    public static func == (lhs: Array, rhs: Array) -> Bool {
        return false
    }
}
extension Array where Element : Equatable {
    public static func == (lhs: Array, rhs: Array) -> Bool {
        return lhs.count == rhs.count && {
            for i in 0..<lhs.count {
                if lhs[i] != rhs[i] {
                    return false
                }
            }
            return true
        }()
    }
}

It works in playgrounds (Xcode 8.1 (8B62)), but I haven’t tested it outside a few trivial cases.

- Dave Sweeris

···

On Nov 15, 2016, at 11:55 PM, Howard Lovatt <howard.lovatt@gmail.com> wrote:

@Dave,

How do I write that though.

I can't write:

    extension Array: Equatable {
        static func ==(lhs: Array, rhs: Array) -> Bool {
            let size = lhs.count
            precondition(rhs.count == size, "The arrays must be the same length")
            for i in 0 ..< size {
                if (lhs[i] as! Equatable) != (rhs[i] as! Equatable) {
                    return false
                }
            }
            return true
        }
    }

Because I can't cast to an Equatable, because Equatable uses Self.

Am I missing something?

-- Howard.

  -- Howard.

On 16 November 2016 at 16:35, David Sweeris <davesweeris@mac.com <mailto:davesweeris@mac.com>> wrote:

> On Nov 15, 2016, at 21:39, Howard Lovatt via swift-users <swift-users@swift.org <mailto:swift-users@swift.org>> wrote:
>
> Hi All,
>
> Does anyone have a good workaround for generics not currently supporting conditional conformance to a protocol. As stated in the Generics Manifesto something like this would be nice:
>
> extension Array: Equatable where Element: Equatable {
> static func ==(lhs: Array, rhs: Array) -> Bool { ... }
> }
>
> But I would currently write a wrapper, something like:
>
> struct ArrayE<T: Equatable> {
> var elements: [T]
> }
> extension ArrayE: Equatable {
> static func ==(lhs: ArrayE, rhs: ArrayE) -> Bool { ... }
> }
>
> This can get unwieldy when there are a lot of conditional protocol extensions required, i.e. wrappers round wrappers.
>
> Is there a better way?
>
> Thanks for any tips,
>
> -- Howard.
> _______________________________________________
> swift-users mailing list
> swift-users@swift.org <mailto:swift-users@swift.org>
> https://lists.swift.org/mailman/listinfo/swift-users

Can you make Array conform to Equatable for any T and then in the == function, if T conforms to Equatable loop the Arrays to check if they're equal, and if it doesn't conform just return false?

I mean, it's still "wrong", but at least you won't get any false positives.

- Dave Sweeris


(Jordan Rose) #4

This does not work. The == dispatch for Arrays is static in this case, not dynamic. You can test this by writing something generic on Equatable.

func same<T: Equatable>(_ x: T, _ y: T) -> Bool { return x == y }
print(same([1], [2]))

Rule of thumb: overloads are resolved statically, protocol requirements are invoked dynamically. You cannot get dynamic behavior out of just overloads, ever.

I don't think there's a way to get this behavior today, unfortunately.

Jordan

···

On Nov 16, 2016, at 7:35, David Sweeris via swift-users <swift-users@swift.org> wrote:

On Nov 15, 2016, at 11:55 PM, Howard Lovatt <howard.lovatt@gmail.com <mailto:howard.lovatt@gmail.com>> wrote:

@Dave,

How do I write that though.

I can't write:

    extension Array: Equatable {
        static func ==(lhs: Array, rhs: Array) -> Bool {
            let size = lhs.count
            precondition(rhs.count == size, "The arrays must be the same length")
            for i in 0 ..< size {
                if (lhs[i] as! Equatable) != (rhs[i] as! Equatable) {
                    return false
                }
            }
            return true
        }
    }

Because I can't cast to an Equatable, because Equatable uses Self.

Am I missing something?

-- Howard.

  -- Howard.

On 16 November 2016 at 16:35, David Sweeris <davesweeris@mac.com <mailto:davesweeris@mac.com>> wrote:

> On Nov 15, 2016, at 21:39, Howard Lovatt via swift-users <swift-users@swift.org <mailto:swift-users@swift.org>> wrote:
>
> Hi All,
>
> Does anyone have a good workaround for generics not currently supporting conditional conformance to a protocol. As stated in the Generics Manifesto something like this would be nice:
>
> extension Array: Equatable where Element: Equatable {
> static func ==(lhs: Array, rhs: Array) -> Bool { ... }
> }
>
> But I would currently write a wrapper, something like:
>
> struct ArrayE<T: Equatable> {
> var elements: [T]
> }
> extension ArrayE: Equatable {
> static func ==(lhs: ArrayE, rhs: ArrayE) -> Bool { ... }
> }
>
> This can get unwieldy when there are a lot of conditional protocol extensions required, i.e. wrappers round wrappers.
>
> Is there a better way?
>
> Thanks for any tips,
>
> -- Howard.
> _______________________________________________
> swift-users mailing list
> swift-users@swift.org <mailto:swift-users@swift.org>
> https://lists.swift.org/mailman/listinfo/swift-users

Can you make Array conform to Equatable for any T and then in the == function, if T conforms to Equatable loop the Arrays to check if they're equal, and if it doesn't conform just return false?

I mean, it's still "wrong", but at least you won't get any false positives.

- Dave Sweeris

You are correct. The work-around is to use two extensions and overload the == operator:

extension Array: Equatable {
    public static func == (lhs: Array, rhs: Array) -> Bool {
        return false
    }
}
extension Array where Element : Equatable {
    public static func == (lhs: Array, rhs: Array) -> Bool {
        return lhs.count == rhs.count && {
            for i in 0..<lhs.count {
                if lhs[i] != rhs[i] {
                    return false
                }
            }
            return true
        }()
    }
}

It works in playgrounds (Xcode 8.1 (8B62)), but I haven’t tested it outside a few trivial cases.


(Howard Lovatt) #5

@Dave,

How do I write that though.

I can't write:

    extension Array: Equatable {
        static func ==(lhs: Array, rhs: Array) -> Bool {
            let size = lhs.count
            precondition(rhs.count == size, "The arrays must be the same
length")
            for i in 0 ..< size {
                if (lhs[i] as! Equatable) != (rhs[i] as! Equatable) {
                    return false
                }
            }
            return true
        }
    }

Because I can't cast to an Equatable, because Equatable uses Self.

Am I missing something?

-- Howard.

  -- Howard.

···

On 16 November 2016 at 16:35, David Sweeris <davesweeris@mac.com> wrote:

> On Nov 15, 2016, at 21:39, Howard Lovatt via swift-users < > swift-users@swift.org> wrote:
>
> Hi All,
>
> Does anyone have a good workaround for generics not currently supporting
conditional conformance to a protocol. As stated in the Generics Manifesto
something like this would be nice:
>
> extension Array: Equatable where Element: Equatable {
> static func ==(lhs: Array, rhs: Array) -> Bool { ... }
> }
>
> But I would currently write a wrapper, something like:
>
> struct ArrayE<T: Equatable> {
> var elements: [T]
> }
> extension ArrayE: Equatable {
> static func ==(lhs: ArrayE, rhs: ArrayE) -> Bool { ... }
> }
>
> This can get unwieldy when there are a lot of conditional protocol
extensions required, i.e. wrappers round wrappers.
>
> Is there a better way?
>
> Thanks for any tips,
>
> -- Howard.
> _______________________________________________
> swift-users mailing list
> swift-users@swift.org
> https://lists.swift.org/mailman/listinfo/swift-users

Can you make Array conform to Equatable for any T and then in the ==
function, if T conforms to Equatable loop the Arrays to check if they're
equal, and if it doesn't conform just return false?

I mean, it's still "wrong", but at least you won't get any false positives.

- Dave Sweeris


(David Sweeris) #6

Aw, nuts! I forgot to try adding another level of, um... genericititty?

Yeah, I think you're right... On the plus side, when conditional conformance hits the wrapper structs can be backed out just by searching your project for their name and replacing it with "Array" (or "Set", etc), right?

- Dave Sweeris

···

On Nov 16, 2016, at 16:35, Jordan Rose <jordan_rose@apple.com> wrote:

On Nov 16, 2016, at 7:35, David Sweeris via swift-users <swift-users@swift.org> wrote:

On Nov 15, 2016, at 11:55 PM, Howard Lovatt <howard.lovatt@gmail.com> wrote:

@Dave,

How do I write that though.

I can't write:

    extension Array: Equatable {
        static func ==(lhs: Array, rhs: Array) -> Bool {
            let size = lhs.count
            precondition(rhs.count == size, "The arrays must be the same length")
            for i in 0 ..< size {
                if (lhs[i] as! Equatable) != (rhs[i] as! Equatable) {
                    return false
                }
            }
            return true
        }
    }

Because I can't cast to an Equatable, because Equatable uses Self.

Am I missing something?

-- Howard.

  -- Howard.

On 16 November 2016 at 16:35, David Sweeris <davesweeris@mac.com> wrote:

> On Nov 15, 2016, at 21:39, Howard Lovatt via swift-users <swift-users@swift.org> wrote:
>
> Hi All,
>
> Does anyone have a good workaround for generics not currently supporting conditional conformance to a protocol. As stated in the Generics Manifesto something like this would be nice:
>
> extension Array: Equatable where Element: Equatable {
> static func ==(lhs: Array, rhs: Array) -> Bool { ... }
> }
>
> But I would currently write a wrapper, something like:
>
> struct ArrayE<T: Equatable> {
> var elements: [T]
> }
> extension ArrayE: Equatable {
> static func ==(lhs: ArrayE, rhs: ArrayE) -> Bool { ... }
> }
>
> This can get unwieldy when there are a lot of conditional protocol extensions required, i.e. wrappers round wrappers.
>
> Is there a better way?
>
> Thanks for any tips,
>
> -- Howard.
> _______________________________________________
> swift-users mailing list
> swift-users@swift.org
> https://lists.swift.org/mailman/listinfo/swift-users

Can you make Array conform to Equatable for any T and then in the == function, if T conforms to Equatable loop the Arrays to check if they're equal, and if it doesn't conform just return false?

I mean, it's still "wrong", but at least you won't get any false positives.

- Dave Sweeris

You are correct. The work-around is to use two extensions and overload the == operator:

extension Array: Equatable {
    public static func == (lhs: Array, rhs: Array) -> Bool {
        return false
    }
}
extension Array where Element : Equatable {
    public static func == (lhs: Array, rhs: Array) -> Bool {
        return lhs.count == rhs.count && {
            for i in 0..<lhs.count {
                if lhs[i] != rhs[i] {
                    return false
                }
            }
            return true
        }()
    }
}

It works in playgrounds (Xcode 8.1 (8B62)), but I haven’t tested it outside a few trivial cases.

This does not work. The == dispatch for Arrays is static in this case, not dynamic. You can test this by writing something generic on Equatable.

func same<T: Equatable>(_ x: T, _ y: T) -> Bool { return x == y }
print(same([1], [2]))

Rule of thumb: overloads are resolved statically, protocol requirements are invoked dynamically. You cannot get dynamic behavior out of just overloads, ever.

I don't think there's a way to get this behavior today, unfortunately.


(Howard Lovatt) #7

Pity nothing else works, looks like I am stuck with multiple wrappers.

I will echo Dave's, Tim's, and Jordan's thoughts, roll on Conditional
Conformance.

Thanks for your help.

  -- Howard.

···

On 17 November 2016 at 09:35, Jordan Rose <jordan_rose@apple.com> wrote:

On Nov 16, 2016, at 7:35, David Sweeris via swift-users < > swift-users@swift.org> wrote:

On Nov 15, 2016, at 11:55 PM, Howard Lovatt <howard.lovatt@gmail.com> > wrote:

@Dave,

How do I write that though.

I can't write:

    extension Array: Equatable {
        static func ==(lhs: Array, rhs: Array) -> Bool {
            let size = lhs.count
            precondition(rhs.count == size, "The arrays must be the same
length")
            for i in 0 ..< size {
                if (lhs[i] as! Equatable) != (rhs[i] as! Equatable) {
                    return false
                }
            }
            return true
        }
    }

Because I can't cast to an Equatable, because Equatable uses Self.

Am I missing something?

-- Howard.

  -- Howard.

On 16 November 2016 at 16:35, David Sweeris <davesweeris@mac.com> wrote:

> On Nov 15, 2016, at 21:39, Howard Lovatt via swift-users < >> swift-users@swift.org> wrote:
>
> Hi All,
>
> Does anyone have a good workaround for generics not currently
supporting conditional conformance to a protocol. As stated in the Generics
Manifesto something like this would be nice:
>
> extension Array: Equatable where Element: Equatable {
> static func ==(lhs: Array, rhs: Array) -> Bool { ... }
> }
>
> But I would currently write a wrapper, something like:
>
> struct ArrayE<T: Equatable> {
> var elements: [T]
> }
> extension ArrayE: Equatable {
> static func ==(lhs: ArrayE, rhs: ArrayE) -> Bool { ... }
> }
>
> This can get unwieldy when there are a lot of conditional protocol
extensions required, i.e. wrappers round wrappers.
>
> Is there a better way?
>
> Thanks for any tips,
>
> -- Howard.
> _______________________________________________
> swift-users mailing list
> swift-users@swift.org
> https://lists.swift.org/mailman/listinfo/swift-users

Can you make Array conform to Equatable for any T and then in the ==
function, if T conforms to Equatable loop the Arrays to check if they're
equal, and if it doesn't conform just return false?

I mean, it's still "wrong", but at least you won't get any false
positives.

- Dave Sweeris

You are correct. The work-around is to use two extensions and overload the
== operator:

extension Array: Equatable {
    public static func == (lhs: Array, rhs: Array) -> Bool {
        return false
    }
}
extension Array where Element : Equatable {
    public static func == (lhs: Array, rhs: Array) -> Bool {
        return lhs.count == rhs.count && {
            for i in 0..<lhs.count {
                if lhs[i] != rhs[i] {
                    return false
                }
            }
            return true
        }()
    }
}

It works in playgrounds (Xcode 8.1 (8B62)), but I haven’t tested it
outside a few trivial cases.

This does not work. The == dispatch for Arrays is static in this case, not
dynamic. You can test this by writing something generic on Equatable.

func same<T: Equatable>(_ x: T, _ y: T) -> Bool { return x == y }
print(same([1], [2]))

Rule of thumb: overloads are resolved statically, protocol requirements
are invoked dynamically. You cannot get dynamic behavior out of just
overloads, ever.

I don't think there's a way to get this behavior today, unfortunately.

Jordan


(Howard Lovatt) #8

That's my plan (to replace my wrappers once conditional conformance is
available). Good news that Conditional Conformance has just been approved.

···

On Thu., 17 Nov. 2016 at 1:30 pm, David Sweeris <davesweeris@mac.com> wrote:

On Nov 16, 2016, at 16:35, Jordan Rose <jordan_rose@apple.com> wrote:

On Nov 16, 2016, at 7:35, David Sweeris via swift-users < > swift-users@swift.org> wrote:

On Nov 15, 2016, at 11:55 PM, Howard Lovatt <howard.lovatt@gmail.com> > wrote:

@Dave,

How do I write that though.

I can't write:

    extension Array: Equatable {
        static func ==(lhs: Array, rhs: Array) -> Bool {
            let size = lhs.count
            precondition(rhs.count == size, "The arrays must be the same
length")
            for i in 0 ..< size {
                if (lhs[i] as! Equatable) != (rhs[i] as! Equatable) {
                    return false
                }
            }
            return true
        }
    }

Because I can't cast to an Equatable, because Equatable uses Self.

Am I missing something?

-- Howard.

  -- Howard.

On 16 November 2016 at 16:35, David Sweeris <davesweeris@mac.com> wrote:

> On Nov 15, 2016, at 21:39, Howard Lovatt via swift-users < > swift-users@swift.org> wrote:
>
> Hi All,
>
> Does anyone have a good workaround for generics not currently supporting
conditional conformance to a protocol. As stated in the Generics Manifesto
something like this would be nice:
>
> extension Array: Equatable where Element: Equatable {
> static func ==(lhs: Array, rhs: Array) -> Bool { ... }
> }
>
> But I would currently write a wrapper, something like:
>
> struct ArrayE<T: Equatable> {
> var elements: [T]
> }
> extension ArrayE: Equatable {
> static func ==(lhs: ArrayE, rhs: ArrayE) -> Bool { ... }
> }
>
> This can get unwieldy when there are a lot of conditional protocol
extensions required, i.e. wrappers round wrappers.
>
> Is there a better way?
>
> Thanks for any tips,
>
> -- Howard.
> _______________________________________________
> swift-users mailing list
> swift-users@swift.org
> https://lists.swift.org/mailman/listinfo/swift-users

Can you make Array conform to Equatable for any T and then in the ==
function, if T conforms to Equatable loop the Arrays to check if they're
equal, and if it doesn't conform just return false?

I mean, it's still "wrong", but at least you won't get any false positives.

- Dave Sweeris

You are correct. The work-around is to use two extensions and overload the
== operator:

extension Array: Equatable {
    public static func == (lhs: Array, rhs: Array) -> Bool {
        return false
    }
}
extension Array where Element : Equatable {
    public static func == (lhs: Array, rhs: Array) -> Bool {
        return lhs.count == rhs.count && {
            for i in 0..<lhs.count {
                if lhs[i] != rhs[i] {
                    return false
                }
            }
            return true
        }()
    }
}

It works in playgrounds (Xcode 8.1 (8B62)), but I haven’t tested it
outside a few trivial cases.

This does not work. The == dispatch for Arrays is static in this case, not
dynamic. You can test this by writing something generic on Equatable.

func same<T: Equatable>(_ x: T, _ y: T) -> Bool { return x == y }
print(same([1], [2]))

Rule of thumb: overloads are resolved statically, protocol requirements
are invoked dynamically. You cannot get dynamic behavior out of just
overloads, ever.

I don't think there's a way to get this behavior today, unfortunately.

Aw, nuts! I forgot to try adding another level of, um... genericititty?

Yeah, I think you're right... On the plus side, when conditional
conformance hits the wrapper structs can be backed out just by searching
your project for their name and replacing it with "Array" (or "Set", etc),
right?

- Dave Sweeris

--
-- Howard.