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
[email protected]
https://lists.swift.org/mailman/listinfo/swift-evolution