And now for something completely different
Stepping back into the meleé after re-grouping for a few days.. I became involved in this pitch for two reasons. It seemed to me a cool idea that Swift could have a modern analogue for how single quoted literals are used in C and Java say i.e. an element of a String and using the integer conversions we could round of a few rough corners in the ergonomics of working with buffers of integers representing text.
If we believe that it is impossible to anticipate the run time segmentation of of a extended grapheme cluster at compile time due to the fluid nature of Unicode and integer conversions have been roundly rejected by the community then the first goal is not possible to achieve and I no longer support the idea of creating a single quoted Character literal let alone Unicode.Scalar literals.
Turning to the second of my goals, the ergonomics: Let's focus on another element of the Core Team's decision -- That the proposal could be broken into two separate proposals:
I'd like to propose a separate, targeted "pure swift" solution to the ergonomics problems, adding code to the standard library rather than the far more involved adding of a new literal type.
Focusing on the original motivations, for me, one problem was the fixed type of the utility initialiser UInt8(ascii: "a"). What if you wanted the more common CChar or Int8 type for your ascii literal? UInt8(ascii: "a") isn't so bad but the more typical CChar(UInt8(ascii: "a")) or UInt16(UInt8(ascii: "a")) is getting inconvenient. This can be solved by writing an extension on FixedWidthInteger and including it in the standard library:
extension FixedWidthInteger {
/// Construct any FixedWidthInteger with value `v.value`.
///
/// - Precondition: `v.value` can be represented as ASCII (0..<128).
@inlinable
@available(swift 5.1)
public init(ascii v: Unicode.Scalar) {
_precondition(v.value < 128,
"Code point value does not fit into ASCII")
self = Self(v.value)
}
}
This would allow Int8(ascii: "a") or UInt16(ascii: "a") or any of the integer types directly. QED.
Looking at the second use case initialising arrays which is particularly inconvenient at the moment I'd suggest a analogous initialiser for arrays extracting ASCII values from characters in a String. This would allow:
let hexcodes = [Int8](ascii: "0123456789abcdef")
The third use case was typically scanning through a buffer of integer values for particular ASCII values, currently:
if cString.advanced(by: 2).pointee == UInt8(ascii: "i") {
The ergonomics of this can be improved by adding an operator to the standard lib for comparison of an integer to a Unicode.Scalar:
public func ==<T: FixedWidthInteger> (lhs: T?, rhs: Unicode.Scalar) -> Bool {
_precondition(rhs.isASCII, "Only ASCII Unicode.Scalar accepted in this context")
return lhs == T(rhs.value)
}
The following would then work:
if cString.advanced(by: 2).pointee == "i" {
I don't feel the implicit assumption that the value used in the comparison is the ASCII value of the character is unreasonable. Number literals are taken to be in base 10 by convention without having to suffix them with .decimalValue or suchlike. We have to draw the line somewhere.
This along with a few other operators to get switch statements working results in a PR that runs to all of 101 lines (including tests) that solves the problem, rather the delicate, staged changes that would be involved introducing a single quoted literal into the compiler and deprecating the old syntax. If someone would like to help turn this into a proposal for review, please get in touch.