[Pre-proposal/Question] Exposing the Unboxing Capabilities of AnyIndex (and similar types)


(Haravikk) #1

So I’m working on a kind of collection wrapper, and hoping to avoid having to expose the underlying type of the collection, by instead returning indices of AnyIndex. This works fine in one direction, but when it comes time to actually use these I have no means of unwrapping them into their original form, this has forced me to basically reimplement AnyIndex myself which seems like a lot of duplicated code.

For those that haven’t looked at the AnyIndex or similar implementation, there are two methods, _unbox() and _unsafeUnbox(), providing access to the underlying type, but these are only exposed internally, so aren’t usable outside of the guts of stdlib.

What I’m wondering is whether there are any strong reasons against exposing these publicly? It could make implementing many wrapper types a lot easier. I know the point of AnyIndex and similar is type-erasure, but with the new collection model we no longer call any methods of the type-erased wrappers themselves, but instead have to pass them around; without unboxing it’s essentially impossible for AnyIndex to be reused for new types, which kind of restricts them to stdlib only. This basically means that AnyIndex is really just a stdlib internal type for implementing the stdlib provided wrapping types, if we want to implement our own we have to create our own AnyFooIndex, which I’m not sure are as efficient (as stdlib also has access to a few other hidden methods for doing this).

If I could just unbox AnyIndex then I could reuse it, rather than reinventing the wheel as it were.

This may apply to other stdlib provided wrappers, but AnyIndex is the one that has stood out for me, as it seems like there’s no real purpose to me being able to wrap values as AnyIndex since there’s very little I can do with them, due to all the useful methods being internal only.

To clarify a bit, if we could unbox AnyIndex we could do something like the following (many methods omitted for brevity):

  struct AnyIndex : Comparable {
    func unbox<T:Comparable>() -> T?
    func unsafeUnbox<T:Comparable>() -> T
  }

  class MyCollectionBox<Base:Collection> : MyCollectionBoxBase<Base.Iterator.Element> {
    let base:Base; init(_ base:Base) { self.base = base }

    var startIndex:AnyIndex { return AnyIndex(base.startIndex) }
    subscript(index:Index) -> Iterator.Element { return index.unsafeUnbox() } // method not available
    …
  }

Unless of course I’m doing it completely wrong for Swift 3, which is entirely possible. Anyway, I was hoping to discuss it, so a proposal can be considered to exposed unboxing methods for AnyIndex (and any other types people think should have these abilities).

I realise that perhaps there is a concern that unboxing could be misused, but I can’t really think of cases where you would try to, since values such as these are only useful when passed back to the type they came from, if you can think of a case where unboxing would be misused then feel free to share it!


(Dave Abrahams) #2

So I’m working on a kind of collection wrapper, and hoping to avoid
having to expose the underlying type of the collection, by instead
returning indices of AnyIndex. This works fine in one direction, but
when it comes time to actually use these I have no means of unwrapping
them into their original form, this has forced me to basically
reimplement AnyIndex myself which seems like a lot of duplicated code.

For those that haven’t looked at the AnyIndex or similar
implementation, there are two methods, _unbox() and _unsafeUnbox(),
providing access to the underlying type, but these are only exposed
internally, so aren’t usable outside of the guts of stdlib.

What I’m wondering is whether there are any strong reasons against
exposing these publicly?

Nope!

It could make implementing many wrapper types a lot easier. I know the
point of AnyIndex and similar is type-erasure, but with the new
collection model we no longer call any methods of the type-erased
wrappers themselves, but instead have to pass them around; without
unboxing it’s essentially impossible for AnyIndex to be reused for new
types, which kind of restricts them to stdlib only. This basically
means that AnyIndex is really just a stdlib internal type for
implementing the stdlib provided wrapping types, if we want to
implement our own we have to create our own AnyFooIndex, which I’m not
sure are as efficient (as stdlib also has access to a few other hidden
methods for doing this).

If I could just unbox AnyIndex then I could reuse it, rather than
reinventing the wheel as it were.

This may apply to other stdlib provided wrappers,

It probably does.

but AnyIndex is the one that has stood out for me, as it seems like
there’s no real purpose to me being able to wrap values as AnyIndex
since there’s very little I can do with them, due to all the useful
methods being internal only.

To clarify a bit, if we could unbox AnyIndex we could do something
like the following (many methods omitted for brevity):

  struct AnyIndex : Comparable {
    func unbox<T:Comparable>() -> T?
    func unsafeUnbox<T:Comparable>() -> T
  }

But those shouldn't be the public names. Perhaps s/box/wrap/ ?

···

on Wed Jun 08 2016, Haravikk <swift-evolution@swift.org> wrote:

  class MyCollectionBox<Base:Collection> :
MyCollectionBoxBase<Base.Iterator.Element> {
    let base:Base; init(_ base:Base) { self.base = base }

    var startIndex:AnyIndex { return
AnyIndex(base.startIndex) }
    subscript(index:Index) -> Iterator.Element { return
index.unsafeUnbox() } // method not available
    …
  }

Unless of course I’m doing it completely wrong for Swift 3, which is
entirely possible. Anyway, I was hoping to discuss it, so a proposal
can be considered to exposed unboxing methods for AnyIndex (and any
other types people think should have these abilities).

I realise that perhaps there is a concern that unboxing could be
misused, but I can’t really think of cases where you would try to,
since values such as these are only useful when passed back to the
type they came from, if you can think of a case where unboxing would
be misused then feel free to share it!
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

--
Dave


(Haravikk) #3

True! So I’m thinking I’ll try to come up with a basic proposal soon, I’m just thinking about how this would be implemented. For example, it may make sense to do this as a protocol that AnyIndex (and other suitable types) can just conform to like so:

  protocol Unwrappable {
    associatedtype UnwrappedType
    func unwrap<T:UnwrappedType>() -> T?
    func unsafeUnwrap<T:UnwrappedType>() -> T
  }

I’ve kept the ability to specify a root type that unwrapping can produce, i.e- Comparable in the case of AnyIndex. Not too happy with the name of UnwrappedType, since it’s not intended to be the exact type in most cases, not sure what would be a more appropriate name. Also I lost track of the discussion about common root types between value and reference types; is there a type in Swift that could be used when unwrapping can produce absolutely anything (struct, enum, object etc.)? If not it may be better to drop the associated type and just lose the extra type-checking benefit.

I’m still struggling to come up with other types that definitely need this, as all the other AnyFoo types I can think of expose functionality of the underlying type that you can use, so the need to unwrap them doesn’t really come up. But with a protocol defining this the capability will be there to expand this quickly to other types later.

···

On 8 Jun 2016, at 20:53, Dave Abrahams via swift-evolution <swift-evolution@swift.org> wrote:

on Wed Jun 08 2016, Haravikk <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

But those shouldn't be the public names. Perhaps s/box/wrap/ ?


(Dave Abrahams) #4

But those shouldn't be the public names. Perhaps s/box/wrap/ ?

True! So I’m thinking I’ll try to come up with a basic proposal soon,
I’m just thinking about how this would be implemented. For example, it
may make sense to do this as a protocol that AnyIndex (and other
suitable types) can just conform to like so:

  protocol Unwrappable {
    associatedtype UnwrappedType
    func unwrap<T:UnwrappedType>() -> T?
    func unsafeUnwrap<T:UnwrappedType>() -> T
  }

I’ve kept the ability to specify a root type that unwrapping can
produce, i.e- Comparable in the case of AnyIndex. Not too happy with
the name of UnwrappedType, since it’s not intended to be the exact
type in most cases,

Examples please?

not sure what would be a more appropriate name.

We have traditionally used the names “Base” and “base” to describe the
name of a type that is being adapted by a wrapper and the property that
accesses the adapted instance.

Also I lost track of the discussion about common root types between
value and reference types; is there a type in Swift that could be used
when unwrapping can produce absolutely anything (struct, enum, object
etc.)?

It's called “Any.”

···

on Thu Jun 09 2016, Haravikk <swift-evolution-AT-haravikk.me> wrote:

On 8 Jun 2016, at 20:53, Dave Abrahams via swift-evolution > <swift-evolution@swift.org> wrote:

on Wed Jun 08 2016, Haravikk <swift-evolution@swift.org > <mailto:swift-evolution@swift.org>> wrote:

If not it may be better to drop the associated type and just lose the
extra type-checking benefit.

I’m still struggling to come up with other types that definitely need
this, as all the other AnyFoo types I can think of expose
functionality of the underlying type that you can use, so the need to
unwrap them doesn’t really come up. But with a protocol defining this
the capability will be there to expand this quickly to other types
later.

--
Dave


(Haravikk) #5

Unfortunately I can’t actually seem to get the above protocol to work, Swift won’t accept the associated type on the generic constraints for the methods, it complains of it being a non-class/non-protocol type. These kinds of tricky generics are very much not a strength of my Swift programming abilities =)

The advantage of being able to declare a base type however is that it helps to disambiguate overloads. For example, consider unwrapping on AnyIndex using the following method:

  public struct AnyIndex : Comparable {
    public func unsafeUnwrap<T:Comparable>() -> T
  }

I then get myself an AnyIndex and decide I’d like to use it in a subscript like so:

  let value = myDictionary[myIndex.unsafeUnwrap()]

Since the unwrapped type is known to at least be of type Comparable, it’s obvious to the compiler which subscript I mean. If however no minimum conformance is provided (i.e- the above method is unsafeUnwrap<T>() -> T) then there are three possibilities (Index, Range and Key, since it’s a dictionary) so it produces an error.
It can still be used by assigning the unwrapped value to a variable with explicit type, but that’s a bit more verbose than I was hoping for.
Of course it can still fail if my AnyIndex isn’t wrapping a DictionaryIndex, but the extra type information at least prevents me from using the unwrapping somewhere that a Comparable can’t possibly be used.

So in my code this so far means I’m having to stick with adding the methods directly to my AnyIndex substitute rather than to a protocol, as I can put the restriction on it this way.

Like I say though, complex generic conformance is something I’m still ropey on (yet I seem to keep finding myself cases that need it) so it’s possible there’s a way to do this that I just can’t think of, or perhaps there are some features on their way that would enable this to work as I’m hoping?

···

On 9 Jun 2016, at 17:11, Dave Abrahams <dabrahams@apple.com> wrote:

on Thu Jun 09 2016, Haravikk <swift-evolution-AT-haravikk.me <http://swift-evolution-at-haravikk.me/>> wrote:

On 8 Jun 2016, at 20:53, Dave Abrahams via swift-evolution >> <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

on Wed Jun 08 2016, Haravikk <swift-evolution@swift.org <mailto:swift-evolution@swift.org> >> <mailto:swift-evolution@swift.org <mailto:swift-evolution@swift.org>>> wrote:

But those shouldn't be the public names. Perhaps s/box/wrap/ ?

True! So I’m thinking I’ll try to come up with a basic proposal soon,
I’m just thinking about how this would be implemented. For example, it
may make sense to do this as a protocol that AnyIndex (and other
suitable types) can just conform to like so:

  protocol Unwrappable {
    associatedtype UnwrappedType
    func unwrap<T:UnwrappedType>() -> T?
    func unsafeUnwrap<T:UnwrappedType>() -> T
  }

I’ve kept the ability to specify a root type that unwrapping can
produce, i.e- Comparable in the case of AnyIndex. Not too happy with
the name of UnwrappedType, since it’s not intended to be the exact
type in most cases,

Examples please?