The post that this one is a reply to (#15) is the last one I read before going on a research binge. Of course, this thread ran its course in the meantime with a lot of new posts. I still want to express my thoughts.
I looked through my old posts for previous thoughts on newtype
/strong type-alias and/or reduced-state types.
- My first versions:
- [early Pitch] retype (or newtype): strong type-alias (typedef) for redoing an interface; also: object aliases
- Trying to grok the ABI documents, w.r.t. strong typedefs
- Invariant checks on strong typedefs?
- [Pitch] strong typedef
- [Pitch] Strong typedef, version 0.2
- [Pitch] Update to Alternative Types (i.e. strong typedef) Proposal
- [Pre-Pitch] Another go at “strong typedef” / alternative types / reduced-state types
- A kernel of another newtype idea
I only looked through threads I started, not one proposed by others (whether or not I participated in those). The last post in that list inspired some of my thoughts below.
Generalizing
A general reduced-state type can accommodate newtype
s by simply not excluding any states. Everyone before my last post in this thread seemed to want only in same-state-count types, so I'm going to restrict myself to that. Some of the later posts do mention wanting to disallow some states of the original type into the new type. I think we should consider reduced-state types later, although there is the problem to duplicating effort if we have separate means of creating same-count newtype
s and reduced-state new types.
Reduced state types also will require us to resolve the co-variance vs. contra-variance for the trampoline members. Technically, same-count new types do have the same issue, but there's more at stake for reduced-state ones.
Declaring Conformance
Unfortunately, I closed the tab, but I read a thread on this forum that copied a response from another thread that stated that the Core Team wants to keep the conformance of a protocol to a type explicit. New ways of declaring/constructing types shouldn't secretly add conformances. I already expressed this wish too, but that other thread lets me emphasize it more. That means the newtype
must have a ": MyProtocol
" somewhere; even if we let a new type copy all of its source's members, it doesn't copy its conformances implicitly.
Note that the new requested feature for tuples to conform to Equatable
, Comparable
, and Hashable
violates this, but it's more limited and may be rectified in later versions of Swift (if added at all) if general tuple conformance is added.
Acknowledging New-Types
This relates to the last thread in my list above.
- Whether or not a type is a
newtype
is an implementation detail. - This means that a new type that has explicit cases will be declared as an
enum
, otherwise as astruct
. There won't be anewtype
sibling construction. - The declaration needs to specify the access level of the
newtype
-ness. For example, if the aliasing is declaredfileprivate
, then anything from a different file will only see the new type as an ordinarystruct
/enum
, and only items within the source file can access the new type'sinit(rawValue:)
initializer for forward conversion and useas
/as!
/as?
for backward conversion.
(Despite the init(rawValue:)
initializer, a new type doesn't conform to RawRepresentable
by default. That conformance has to be explicitly added. If it's added then the new type's newtype
-ness has to have the same publicity as the new type.)
Expression of newtype
-ness
I'm thinking of a form like:
struct MyType: @newtype(internal) MySourceType, MyProtocol { /*...*/ }
where the access level may be omitted. If it is, the default is the most restrictive of the new type's publicity, the source type's publicity, and internal
. Remember that access level determines who can see init(rawValue:)
and use as
/as!
/as?
.
The source type may be a struct
, enum
, tuple, or a single enum
case. The last one has the same interaction model as a tuple of the same shape, but the storage is the source's enum
type. (Any other structural value types we add, like sum-tuples or fixed-sized arrays, may also be a source type.) Within any members of the new type, access to the internal state as the source type is done through "super
". If we specify RawRepresentable
conformance without giving a definition, we will have rawValue
automatically forward to super
.
If the source type is a single enum
case, the core initializer is fail-able, where it fails only if the input is of a different case.
For the items that can see the new type's as
/as!
/as?
, their code can reinterpret an instance of the new type as the source type with "as
". If the source type is itself a new type, it can convert down the "inheritance" chain. It can cross- or up-convert to a different new type that shares an ancestor. (The last two reinterpretations apply only when the original item's code has enough access to see the destination type's "inheritance" chain, at least to the common ancestor). If the up-cast phase of an reinterpretation involves an enum
case, then "as?
" or "as!
" is required.
If the new type is a struct
, then any items that cannot see the init(rawValue:)
initializer can only reuse values you give a high-enough access to, or create values through functions you give a high-enough access to. (You can make this an empty list to keep your new type practically private.) If the new type is an enum
and the source is an enum
too, then you get the same case
s, but you can override the names:
enum MyType: @newtype MyOldEnum {
// Prototype syntax
publish case old(hello: Int, Double) as new(Int, world: Double)
}
If a new case name conflicts with a different case of the source, then you need to rename that other old case too. If the new type is an enum
and the source is anything else, then you need to specify the name of the sole case:
enum MyType: @newtype MyOldStruct {
publish super as single
}
That sole case will have a payload with one unlabeled item of the source type. (If the source is an enum
case, the payload is one item of the equivalent tuple.)
I'll leave specification of trampoline members for a later thread.
Addition: I forgot to write about new-typing a singular enum
case. They work the same as if Void
was their equivalent tuple.