[swift-evolution-announce] [Review] SE-0089: Replace protocol<P1, P2> syntax with Any<P1, P2>


(Jon Hull) #1

If your code has many manual type erasing wrappers corresponding to
protocols with associated types and/or Self requirements that also never
have to trap type mismatches, that would certainly be instructive
empirical data. Would you care to share the protocols and wrappers you
are talking about?

This code is a bit embarrassing (I wrote most of it as I was still learning Swift), but if it is helpful I will share:
https://gist.github.com/jonhull/639e756ad5228348f93f40f06169588c

It doesn’t trap anywhere (that I know about). Some of the code which calls it does throw an error in the case of mismatched types (but it doesn’t/shouldn't crash). Most functions which use it are generic on the associatedType.

It does work fairly well though (even on the AppleWatch). The main issue is that I have no way to persist the values which have been drawn into the type-erased world (or even the value-type world, really).

Also, if anyone has a better way to write the type erasing wrapper, I would love to hear it. These are very memory intensive…

Thanks,
Jon


(Jon Hull) #2

Here is another example from a current project: (Here there is an option to trap, if desired… but many more options not to)
(Note: The commented out bit was an attempt to get the compiler to play nicer with intuiting the type, but it didn’t help… leaving as data)

struct HashableBox:Hashable {
    private let _value:Any
    private let _hash:Int
    private let _eq:(Any)->Bool
    
    enum Error:ErrorType {
        case typeMismatch
    }
    
    init<T:Hashable>(_ value:T){
        self._value = value
        self._hash = value.hashValue
        self._eq = { other in
            guard let otherT = other as? T else {return false}
            return value == otherT
        }
    }
    
    func valueOrCrash<T:Hashable>(msg:String? = nil) -> T {
        guard let value = _value as? T else {
            let msg = msg ?? "Attempt to retrieve value of type \(self._value.dynamicType) as \(T.self)"
            fatalError(msg)
        }
        return value
    }
    
    func valueOrNil<T:Hashable>() -> T? {//(type:T.Type = T.self) -> T? {
        return self._value as? T
    }
    
    func valueOrError<T:Hashable>()throws -> T {
        guard let value = _value as? T else {throw Error.typeMismatch}
        return value
    }
    
    var asAny:Any {
        return _value
    }
    
    var hashValue: Int {
        return _hash
    }
}

func == (lhs:HashableBox, rhs:HashableBox) -> Bool {
    return lhs._eq(rhs)
}

Thanks,
Jon

···

On Jun 11, 2016, at 3:25 AM, Jonathan Hull <jhull@gbis.com> wrote:

If your code has many manual type erasing wrappers corresponding to
protocols with associated types and/or Self requirements that also never
have to trap type mismatches, that would certainly be instructive
empirical data. Would you care to share the protocols and wrappers you
are talking about?

This code is a bit embarrassing (I wrote most of it as I was still learning Swift), but if it is helpful I will share:
https://gist.github.com/jonhull/639e756ad5228348f93f40f06169588c

It doesn’t trap anywhere (that I know about). Some of the code which calls it does throw an error in the case of mismatched types (but it doesn’t/shouldn't crash). Most functions which use it are generic on the associatedType.

It does work fairly well though (even on the AppleWatch). The main issue is that I have no way to persist the values which have been drawn into the type-erased world (or even the value-type world, really).

Also, if anyone has a better way to write the type erasing wrapper, I would love to hear it. These are very memory intensive…

Thanks,
Jon


(Dave Abrahams) #3

If your code has many manual type erasing wrappers corresponding to
protocols with associated types and/or Self requirements that also never
have to trap type mismatches, that would certainly be instructive
empirical data. Would you care to share the protocols and wrappers you
are talking about?

This code is a bit embarrassing (I wrote most of it as I was still
learning Swift), but if it is helpful I will share:
https://gist.github.com/jonhull/639e756ad5228348f93f40f06169588c

It doesn’t trap anywhere (that I know about). Some of the code which
calls it does throw an error in the case of mismatched types (but it
doesn’t/shouldn't crash).

If that error is being thrown to handle what you consider to be a
programming error, that is the moral equivalent of a trap.

Most functions which use it are generic on the associatedType.

It does work fairly well though (even on the AppleWatch). The main
issue is that I have no way to persist the values which have been
drawn into the type-erased world (or even the value-type world,
really).

Also, if anyone has a better way to write the type erasing wrapper, I
would love to hear it. These are very memory intensive…

It looks like you could make some gains by, instead of storing a closure
for each operation, using a base class with a method for each operation,
per the box types in
https://github.com/apple/swift/blob/master/stdlib/public/core/ExistentialCollection.swift.gyb

HTH,

···

on Sat Jun 11 2016, Jonathan Hull <jhull-AT-gbis.com> wrote:
--
Dave


(Thorsten Seitz) #4

Here is another example from a current project: (Here there is an option to trap, if desired… but many more options not to)
(Note: The commented out bit was an attempt to get the compiler to play nicer with intuiting the type, but it didn’t help… leaving as data)

struct HashableBox:Hashable {
    private let _value:Any
    private let _hash:Int
    private let _eq:(Any)->Bool
    
    enum Error:ErrorType {
        case typeMismatch
    }
    
    init<T:Hashable>(_ value:T){
        self._value = value
        self._hash = value.hashValue
        self._eq = { other in
            guard let otherT = other as? T else {return false}
            return value == otherT
        }
    }

I think there is an error in the implementation of _eq, because it should try to unpack the value as T and not the box.

HashableBox(1) == HashableBox(1) should be true IMO but is false with your implementation

This works as I would expect:

    private let _eq:(HashableBox)->Bool

    init<T:Hashable>(_ value:T){
        self._value = value
        self._hash = value.hashValue
        self._eq = { other in
            guard let otherValue: T = other.valueOrNil() else { return false }
            return value == otherValue
        }
    }

-Thorsten

···

Am 11.06.2016 um 13:22 schrieb Jonathan Hull via swift-evolution <swift-evolution@swift.org>:

    func valueOrCrash<T:Hashable>(msg:String? = nil) -> T {
        guard let value = _value as? T else {
            let msg = msg ?? "Attempt to retrieve value of type \(self._value.dynamicType) as \(T.self)"
            fatalError(msg)
        }
        return value
    }
    
    func valueOrNil<T:Hashable>() -> T? {//(type:T.Type = T.self) -> T? {
        return self._value as? T
    }
    
    func valueOrError<T:Hashable>()throws -> T {
        guard let value = _value as? T else {throw Error.typeMismatch}
        return value
    }
    
    var asAny:Any {
        return _value
    }
    
    var hashValue: Int {
        return _hash
    }
}

func == (lhs:HashableBox, rhs:HashableBox) -> Bool {
    return lhs._eq(rhs)
}

Thanks,
Jon

On Jun 11, 2016, at 3:25 AM, Jonathan Hull <jhull@gbis.com <mailto:jhull@gbis.com>> wrote:

If your code has many manual type erasing wrappers corresponding to
protocols with associated types and/or Self requirements that also never
have to trap type mismatches, that would certainly be instructive
empirical data. Would you care to share the protocols and wrappers you
are talking about?

This code is a bit embarrassing (I wrote most of it as I was still learning Swift), but if it is helpful I will share:
https://gist.github.com/jonhull/639e756ad5228348f93f40f06169588c

It doesn’t trap anywhere (that I know about). Some of the code which calls it does throw an error in the case of mismatched types (but it doesn’t/shouldn't crash). Most functions which use it are generic on the associatedType.

It does work fairly well though (even on the AppleWatch). The main issue is that I have no way to persist the values which have been drawn into the type-erased world (or even the value-type world, really).

Also, if anyone has a better way to write the type erasing wrapper, I would love to hear it. These are very memory intensive…

Thanks,
Jon

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


(Jon Hull) #5

If your code has many manual type erasing wrappers corresponding to
protocols with associated types and/or Self requirements that also never
have to trap type mismatches, that would certainly be instructive
empirical data. Would you care to share the protocols and wrappers you
are talking about?

This code is a bit embarrassing (I wrote most of it as I was still
learning Swift), but if it is helpful I will share:
https://gist.github.com/jonhull/639e756ad5228348f93f40f06169588c

It doesn’t trap anywhere (that I know about). Some of the code which
calls it does throw an error in the case of mismatched types (but it
doesn’t/shouldn't crash).

If that error is being thrown to handle what you consider to be a
programming error, that is the moral equivalent of a trap.

Agreed. In this case the error is thrown when two different user inputs don’t match types, and it results in a user facing error. It basically functions as input validation.

It seems to me that Swift has four ways of handling failure:
1) Disallow it entirely
2) Trap (marked with !)
3) Throw (marked with try)
4) Return nil (marked with ?)

It seems to me that the Swift-y™ way to handle this is to disallow it until the programmer chooses the most appropriate of the other three options.

It may be that trapping is the right thing to do the majority of the time, but I definitely have use-cases where returning nil is the correct option.

Most functions which use it are generic on the associatedType.

It does work fairly well though (even on the AppleWatch). The main
issue is that I have no way to persist the values which have been
drawn into the type-erased world (or even the value-type world,
really).

Also, if anyone has a better way to write the type erasing wrapper, I
would love to hear it. These are very memory intensive…

It looks like you could make some gains by, instead of storing a closure
for each operation, using a base class with a method for each operation,
per the box types in
https://github.com/apple/swift/blob/master/stdlib/public/core/ExistentialCollection.swift.gyb

Thank you, much appreciated.

···

On Jun 12, 2016, at 8:31 PM, Dave Abrahams <dabrahams@apple.com> wrote:
on Sat Jun 11 2016, Jonathan Hull <jhull-AT-gbis.com> wrote:

HTH,
--
Dave


(Jon Hull) #6

Yikes! I had intended to call rhs._value in ==. Thank you :slight_smile: You saved me a lot of debugging time. I ended up fixing with your version.

You can see why I would love to have Swift do the thunk-ing for me… less opportunity to shoot myself in the foot with Any.

Thanks,
Jon

···

On Jun 11, 2016, at 5:25 AM, Thorsten Seitz <tseitz42@icloud.com> wrote:

Am 11.06.2016 um 13:22 schrieb Jonathan Hull via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>>:

Here is another example from a current project: (Here there is an option to trap, if desired… but many more options not to)
(Note: The commented out bit was an attempt to get the compiler to play nicer with intuiting the type, but it didn’t help… leaving as data)

struct HashableBox:Hashable {
    private let _value:Any
    private let _hash:Int
    private let _eq:(Any)->Bool
    
    enum Error:ErrorType {
        case typeMismatch
    }
    
    init<T:Hashable>(_ value:T){
        self._value = value
        self._hash = value.hashValue
        self._eq = { other in
            guard let otherT = other as? T else {return false}
            return value == otherT
        }
    }

I think there is an error in the implementation of _eq, because it should try to unpack the value as T and not the box.

HashableBox(1) == HashableBox(1) should be true IMO but is false with your implementation

This works as I would expect:

    private let _eq:(HashableBox)->Bool

    init<T:Hashable>(_ value:T){
        self._value = value
        self._hash = value.hashValue
        self._eq = { other in
            guard let otherValue: T = other.valueOrNil() else { return false }
            return value == otherValue
        }
    }

-Thorsten

    func valueOrCrash<T:Hashable>(msg:String? = nil) -> T {
        guard let value = _value as? T else {
            let msg = msg ?? "Attempt to retrieve value of type \(self._value.dynamicType) as \(T.self)"
            fatalError(msg)
        }
        return value
    }
    
    func valueOrNil<T:Hashable>() -> T? {//(type:T.Type = T.self) -> T? {
        return self._value as? T
    }
    
    func valueOrError<T:Hashable>()throws -> T {
        guard let value = _value as? T else {throw Error.typeMismatch}
        return value
    }
    
    var asAny:Any {
        return _value
    }
    
    var hashValue: Int {
        return _hash
    }
}

func == (lhs:HashableBox, rhs:HashableBox) -> Bool {
    return lhs._eq(rhs)
}

Thanks,
Jon

On Jun 11, 2016, at 3:25 AM, Jonathan Hull <jhull@gbis.com <mailto:jhull@gbis.com>> wrote:

If your code has many manual type erasing wrappers corresponding to
protocols with associated types and/or Self requirements that also never
have to trap type mismatches, that would certainly be instructive
empirical data. Would you care to share the protocols and wrappers you
are talking about?

This code is a bit embarrassing (I wrote most of it as I was still learning Swift), but if it is helpful I will share:
https://gist.github.com/jonhull/639e756ad5228348f93f40f06169588c

It doesn’t trap anywhere (that I know about). Some of the code which calls it does throw an error in the case of mismatched types (but it doesn’t/shouldn't crash). Most functions which use it are generic on the associatedType.

It does work fairly well though (even on the AppleWatch). The main issue is that I have no way to persist the values which have been drawn into the type-erased world (or even the value-type world, really).

Also, if anyone has a better way to write the type erasing wrapper, I would love to hear it. These are very memory intensive…

Thanks,
Jon

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution