Here’s a quick review of a strong-“typedef” idea I’ve mentioned here:
typecopy MyNewType: OriginalValueType, AnyProtocolsIWant {
// Direct member declarations
// “publish MemberFromOriginalType” for all overloads (or the singular item for
non-functions)
// “publish MemberWithSignatureFromOriginalType” to pick out one subset of
overloads
// “publish MemberWithSignatureAndParameterNamesFromOriginalType” to pick out
one overload
// “publish ProtocolThatOriginalTypeConfromsTo"
}
(The name “typecopy” is changed from “alter”.)
All type-copies automatically conform to the new system AnyTypeCopy protocol,
which derives from RawRepresentable. It just adds the “CoreRawValue” type. This
type alias matches RawValue if that is a non-typecopy; otherwise it aliases
that type copy’s CoreRawValue type. AnyTypeCopy cannot be added to non-typecopy
types. Any protocol that derives from it can only be applied to typecopy types.
Type-copies are value types, and can only shadow structures, enumerations,
other type-copies, tuples, and fixed-size arrays (if added).
The initializer used to conform to RawRepresentable is the type’s designated
initializer. It’s the only one that can access “super.” Any other initializers
added must be convenience and reference the designated one. (Published
initializers are considered convenience.) The designated initializer can be
fail-able to model subtypes. The designated initializer can pass on an altered
value to model quotient types (or any other crazy mapping scheme); including
being fail-able too (i.e. a quotient type of a subtype).
Of course, the designated initializer could do neither changes nor filtering:
typecopy MyResourceID: Int16, Hashable {
init(rawValue: RawValue) { super = rawValue }
publish Equatable
var hashValue: Int { get {return rawValue.hashValue & 0x01} }
}
Since every valid state of Int16 is also a valid state of MyResourceID, and
without remapping, this type-copy is a trivial copy of its source. Besides
downcasts (and cross-casts) never being fail-able, all sorts of other
type-punning and related optimizations should be possible. (Upcasts are always
trivial, even when the designated initializer isn’t.) We should be able to
easily copy/type-pun between Array<MyResourceID> with Array<T>, where T is
either Int16 or another type-copy that (eventually) trivially shadows Int16.
(Of course, this applies to Unsafe*Pointer or fixed-size arrays (if added)
too.) These optimizations can’t be done with non-trivial type copies, since
downcasts (and the downcast parts of cross-casts) need to have their designated
initializers called for each element.
1. Now we get to the question in the Subject. Before now, I just wanted the
compiler to see the definition of the designated initializer to determine if
the type-copy is trivial or not. Now I realize that may have problems. One is
that it may not be easy for the compiler to check if a given initializer
matches the form I gave in the “MyResourceID” example. Another is that if the
user prints out a prototype summary of the type, with members but without their
definitions, s/he would have no idea if the type-copy was trivial or not.
I’m wondering if we should define trivial-ness with a keyword added to the
definition:
typecopy MyResourceID1 trivial: Int16, Hashable {
// Initializer not given
publish Equatable
var hashValue: Int { get{return rawValue.hashValue & 0x01} }
}
typecopy MyResourceID2: Int16, Hashable {
trivial init(rawValue: RawValue) // Looks incomplete, but it’s actually the
full thing
publish Equatable
var hashValue: Int { get{return rawValue.hashValue & 0x01} }
}
typecopy MyResourceID3: Int16, Hashable {
init(rawValue: RawValue) = trivial
publish Equatable
var hashValue: Int { get{return rawValue.hashValue & 0x01} }
}
I kind-of like the look of the second, but it may be confusing to (new) users
since there’s no code block after the initializer declaration. My next-liked
look is the first one, but I’m not sure where the “trivial” should go (before
“typecopy”, after it, current spot, or before the original type’s name after
the colon). The third choice looks the weirdest to me, but it’s probably the
easiest parsing-wise. It does remind me of the C++ class built-in
life-management overrides.
(Now completing the post, maybe option 3 isn’t the easiest to parse. We go from
“init” then Code-Block to “init” then either Code-Block or “= trivial”.)
For actual use, I’m leaning on placing “trivial” right before the original
type’s name but after the colon. What does everyone else think?
1a. If “trivial” is added to the “typecopy” line, should explicitly defining a
designated initializer anyway be banned? I’m leaning towards yes.
2. If no initializers are declared, either directly or with a publish, then a
designated initializer is automatically added. It would have the same form as
the one I gave in the example. If “trivial” goes inside the initializer’s
declaration instead of the “typecopy” line, then the initializer is declared
trivial (if possible). If no direct initializers are given, but at least one is
added through a “publish,” should an automatic designated initializer still be
synthesized, or should the user be forced to declare one?
If we’re going to use “trivial” on the “typecopy” line and ban explicitly
defining a designated initializer anyway, then the user shouldn’t (and
can’t(!)) be forced to declare one. Otherwise, I think the user should be
forced to manually declare the designated initializer if any others are either
published or directly declared. It would get weird in one case: if there are no
initializers directly declared or published, a protocol is published, and then
that protocol is changed to add an initializer; suddenly the user would have to
ensure a designated initializer in their code.
3. I finished all of the preceding text, and realized a new problem. There’s no
way to determine if a given type-copy is trivial in code, in a meta-programming
way. If there’s a way for a derived protocol to restrict how its parent’s
members are expressed, then we could define a AnyTrivialTypeCopy as a
sub-protocol, but only if we can update the “init?(rawValue: RawValue)” to
always be “init(rawValue: RawValue)”. (AnyTrivialTypeCopy would be
automatically added to type-copy types that are trivial. It can be manually
added to parameters or protocols, but not types unless they already qualify.)
Or we could have an “isTrivial” type-level constant Bool property. A separate
type-traits helper, like the one for memory layout? Other ideas?
—
Daryle Walker
Mac, Internet, and Video Game Junkie
darylew AT mac DOT com
_______________________________________________
swift-evolution mailing list
[email protected]
https://lists.swift.org/mailman/listinfo/swift-evolution