(Warning: rambling stream-of-conscious mode while slightly tired)
The names of new keywords and identifiers are subject to bike-shedding.
1. Set-up Protocols
protocol HardRawRepresentable: RawRepresentable {
init(rawValue: RawValue)
}
Just like RawRepresentable, but the initializer has to be non-failable (“init?”
and “init!” are no longer options).
protocol RawProcessable: RawRepresentable {
mutating func withMutableRawValue<R>(_ body: (inout RawValue) throws -> R )
rethrows -> R
}
Maybe someone will find a use for this outside of type-copies.
2. Type-copy Type Syntax
A “typecopy” puns another value type, either nominal (structures, enumerations,
or other type-copies) or structural (tuples, and fixed-size arrays if added). I
guess it’d be implemented like a structure with a single instance-level stored
property or an enumeration without indirect and/or associated values. Unlike a
type alias, a type-copy is considered distinct from its underlying type and
conversions have to be explicit casts. It has no operations by default besides
copying (which every type has); adaptations of the underlying type’s interface
have to be explicitly copied, and can be selective.
typecopy NewType: SPECIAL-ATTRIBUTES UnderlyingType, DesiredProtocols {
// Anything other nominal types have, except enumeration cases and
instance-level stored properties.
// Also have new syntax to copy parts of the underlying type’s interface.
}
All type-copies conform to RawProcessible; the particulars depend on the
special attributes (which are conditional keywords).
The initializer that satisfies RawRepresentable is the type’s designated
initializer. It cannot be defined by the user; only secretly by the system;
it’s an error to introduce one anyway in the primary definition block, an
extension, or a default implementation from an added protocol. Any
user-declared initializer that doesn’t forward to another user-declared
initializer must forward to the designated initializer.
2a. No Special Attributes
The type must define a method called
static func #map(rawValue: RawValue) -> RawValue?
I’m using “#map” as the name so we don’t take out any more identifiers from the
user. (The existing ones, “self,” “init,” “super,” and “Self,” were already
done by Objective-C.) The function must be pure relative to the input; it can
call non-impurities, like “print,” but the return value must depend only on the
argument and not any global state. The user cannot call this function, only the
system (but it can be implemented with regular, accessible functions you do
write); let’s call this accessibility “reallyprivate". The designated
initializer is in the “init?” form and calls this special method for the
initial state.
The underlying state is accessed with this stored property:
pp reallyprivate(set) var rawValue: RawValue
where “pp” is the type’s accessibility level.
The implementation of “withMutableRawValue” copies “rawValue” to a mutable
copy, calls the closure passing that copy, then assigns “Self(rawValue:
newCopy)!” to self, causing a runtime error if the initialization part fails.
2b. The “injective” Attribute
This means that the mapping function must be a total function, and not partial:
static func #map(rawValue: RawValue) -> RawValue
Every legal state of RawValue is usable in the new type. (It may not be stored
in the new type; the mapping function can be surjective.) These type-copies
also conform to HardRawRepresentable.
The main external effect of this attribute is that downcasts from the
underlying type to the new type use unconditional-“as”. (Downcasts and
cross-casts from a type that shares the same implementation type also use
unconditional-as if all the downcast phase links also use unconditional-as.)
2c. The “selfIdeal” Attribute
This means that all approved input states map to themselves in the storage
state, this changes the required secret method to:
static func #approve(rawValue: RawValue) -> Bool
It effectively acts like case [2a] where “#map” calls “#approve” and returns
either the input for TRUE or NIL for FALSE.
The main external effect of this attribute is that downcasts (and the downcast
phase of cross-casts) can be trivially done with direct type punning, assuming
all the “#approve” calls AND to TRUE, instead of possibly longer “#map” calls.
2d. Using “injective” and “selfIdeal” together
This combination means every input state is accepted, and map to themselves;
this is exactly a “strong typedef”. Such types conform to HardRawRepresentable.
There is no special method required; the value is just copied in without
wasting time calling the bijective identity function. And the “rawValue”
property has its “set” just as public as its “get”. (Technically, you can
change “rawValue” by setting it directly or calling “withMutableRawValue”.)
3. Why would I want this?
The original inspiration was my first fixed-size array ideas have a simple
structural version and a complex nominal version. Then I thought it would be
better to keep arrays simple and move the nominal stuff to a generalized
concept of augmentation. I also read about the C++ guys trying to add this idea
in.
(An inspiration was reading that C++ requires std::complex to have the same
layout as T[2] and allow reinterpret-casts between them, for reading in a raw
array of numbers and converting them easily to complex numbers. I was thinking
Swift-y of reading in a fixed-size array of ten Double (i.e. [10; Double]),
reshaping it to [5; [2; Double]], then map with pun-cast to [5;
Complex<Double>]. Here, Complex<T> would be a type-copy of [2; T].)
I got additional inspiration by reading "Quotient Types for Programmers” at
<http://www.hedonisticlearning.com/posts/quotient-types-for-programmers.html>.
Type-copies can be used for subset types and quotient types (and weird combo
subsetted quotient types).
This past autumn, Apple’s latest operating systems’ APIs changed their
String-ly typed values for its Views, Notification Centers, and such to use
manual versions of this idea. Maybe they could change them to this.
4. Detection
How would I know if a given type is a type-copy? The protocols generally can be
used for regular work, so testing them won’t help. (That was a change from an
earlier idea.) I realized that maybe we can move them to “Mirror”. Add a
“`typecopy`” case to “Mirror.DisplayStyle”. Add an “underlyingTypeMirror”
property like the super-class one; it would be NIL if the target type isn’t
actually a type-copy. Maybe add a global “implementationType(of:)” function
that returns the type-copy’s implementation type (The underlying type can
already be found with “RawValue”.); should calling this on a non-typecopy
return NIL or itself?
Maybe the actual printing could be like: “MyType punning(“ + Mirror of
self.rawValue + “)”.
I guess you could check for HardRawRepresentable to suspect a type is a
injective type-copy, but there’s no way to test for self-ideal type-copies.
Would that be a problem? Could it be fixed (without a lot of extra compiler
magic)?
5. Trampolines
I don’t have any new ideas on how to declare a type-copy is reusing an
interface from its underlying type. However, the “withMutableRawValue” method
now provides a way for copied methods (manually or using “publish” for
automatically) to actually work.
—
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