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

Reply via email to