In one of my earlier fixed-size array proposals, I had two kinds of array 
types: a compound type and a nominal type. I dropped the latter because there 
wasn’t too much of a value-add, especially once I copied its unique features to 
the compound type. It would be like a compound array property wrapped by a 
struct, except that member wouldn’t have a proper name so you would have to use 
“super” to refer to the inner object.

I have been looking up on how Rust handles its array/vector types, and I saw an 
issue on adding complex numbers. I then recalled that “long” ago I read up on 
C++’s complex numbers and how that class isn’t supposed to have any padding so 
you can alias a large array of double into a large-ish array of complex. That 
could have been resolved another way if we had a strong “typedef” so a new type 
would have the EXACT layout as another type without wrapping it in a struct and 
risking padding.

Over the past few weeks, I seen requests here for a strong type-alias. And 
requests for “I know that tuples are supposed to be protocol-less, but I want 
to (automatically) slap SuperReallyPeachyKeenProtocol on them anyway”. Now I 
think I can put all of these together.

Name a New Type Based on an Existing One:

“retype” IDENTIFIER “:” ORIGINAL-TYPE (“,” PROTOCOL )* “{“
    // Whatever, including exports…
“}”

If there is nothing in the braces, then the new type doesn’t any operations 
(besides the default assignment and “&” for function in-out parameters). You 
can get a reference to the object as the original type as “myNewObject.super”; 
it will have the same let/var status as the outer object’s declaration. Access 
to “super” lets you implement whatever new interface you want. Of course, you 
can implement new protocols added to the type list. The new type is implemented 
as the original one; no wrapping structs added; the same size, stride, and 
alignment.

You can republish parts of the old type’s interface. You use an “export 
OLD-NAME” declaration. There is also an “export default” declaration, which 
does:

* The application operator (i.e. “(whatever)”) for a function type
* The “.0”, “.1”, etc. member access for a tuple type
* All the original cases for an enumeration type
* Nothing for any other type

The default-export is optional for tuple and enumeration types, since you can 
export individual members or cases. Since there is no way to express the 
application operator in Swift, a function type’s retype has to use the 
default-export if it wants to export anything. All of the original type’s 
implementation of one of its direct or indirect protocols can be exported with 
“export TheOldProtocol”; but the new type won’t officially support the protocol 
unless it’s (directly or indirectly) in the new type's protocol list.

How do you initialize objects of this type? Besides using another object of the 
same type, you can use an object of the original type or of a retype that 
shares the implementation type as an initializer. If the initialization happens 
after the object’s declaration, then “expressionOfOtherType as MyReType” syntax 
has to be used. (Post-declaration initialization can use “myObject.super” 
instead.)

Now, I can do something like (assuming fixed-size arrays are added):

retype Complex<T: SomeNumericProtocol>: [2: T], Equatable /*, etc.*/ {
var re: T {
get { return super[0] }
set { super[0] = newValue }
}
var im: T {
get { return super[1] }
set { super[1] = newValue }
}

init(_: UninitializedFlag) {  // “UninitializedFlag” is a library enum type 
with case “.uninitialized"
super = []  // Leave uninitialized
}
init(real: T = 0, imaginary: T = 0) {
super = [real, imaginary]
}
//…
}

func someFunc() {
let scalars: [_: Double] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
let rawComplexes = scalars as [5: [2: Double]]  // Reshape command
let firstComplex = rawComplexes[0] as Complex  // May have to use 
“Complex<Double>” if the “as” is too indirect.
//...
}

Object Aliasing

But what if I want to work on those scalars directly? What if I want to mutate 
my complex number objects and update the scalars too? What if we add a “pose” 
declaration, at the same level as “let” and “var”?

var scalars: [_: Double] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
pose rawComplexes: [5: [2: Double]] = scalars  // OK as they have the same 
stride and inner non-array type
pose complexes: [5: Complex] = rawComplexes

complexes[0].re = -11
assert(scalars[0] == -11)

The source of a pose has to have an equal or greater scope (and lifetime) than 
the alias. (Instance- and type-level properties of a type definition are 
considered separately. An instance-level property can pose over a type-level 
one. A function-scope object can pose over a function argument, as long as it 
doesn’t try to persist after function-end.) A pose has the same let/var status 
as its source. (The source may not be directly marked as “let” or “var,” it may 
be a “pose” itself.) A pose always has to be initialized at declaration time 
with its source.

[Note: the “pose” idea sprang into my head while writing the “retype” one. So 
you can ignore “pose” if you want to focus on “retype” first.]

— 
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