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
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

Reply via email to