> On Jul 30, 2016, at 10:35 PM, Jacob Bandes-Storch via swift-evolution
> <[email protected]> wrote:
>
> In the past, there has been some interest in refining the behavior of
> ExpressibleByStringInterpolation (née StringInterpolationConvertible), for
> example:
>
> - Ability to restrict the types that can be used as interpolation segments
> - Ability to distinguish the string-literal segments from interpolation
> segments whose type is String
>
> Some prior discussions:
> - "StringInterpolationConvertible and StringLiteralConvertible inheritance"
> https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20160516/017654.html
> - Sub-discussion in "Allow multiple conformances to the same protocol"
> https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20160606/020746.html
> - "StringInterpolationConvertible: can't distinguish between literal
> components and String arguments" https://bugs.swift.org/browse/SR-1260 /
> rdar://problem/19800456&18681780
> - "Proposal: Deprecate optionals in string interpolation"
> https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20160516/018000.html
>
>
> About Swift 4, Chris wrote:
> - String re-evaluation: String is one of the most important fundamental
> types in the language. The standard library leads have numerous ideas of how
> to improve the programming model for it, without jeopardizing the goals of
> providing a unicode-correct-by-default model. Our goal is to be better at
> string processing than Perl!
>
> I'd be interested in any more detail the team can provide on this. I'd like
> to talk about string interpolation improvements, but it wouldn't be wise to
> do so without keeping an eye towards possible regex/pattern-binding syntax,
> and the String refinements that the stdlib team has in mind, if there's a
> chance they would affect interpolation.
>
> Discuss!
I'm not one of the core team, so all I can really provide is a use case.
Given a LocalizedString type like:
/// Conforming types can be included in a LocalizedString.
protocol LocalizedStringConvertible {
/// The format to use for this instance. This format string will be
included in the key when
/// this type is interpolated into a LocalizedString.
var localizedStringFormat: String { get }
/// The arguments to use when formatting to represent this instance.
var localizedStringArguments: [CVarArg] { get }
}
extension NSString: LocalizedStringConvertible {…}
extension String: LocalizedStringConvertible {…}
extension LocalizedString: LocalizedStringConvertible {…}
extension Int: LocalizedStringConvertible {…}
// etc.
struct LocalizedString {
/// Initializes a LocalizedString by applying the `arguments` to the
format string with the
/// indicated `key` using `String.init(format:arguments:)`.
///
/// If the `key` does not exist in the localized string file, the `key`
itself will be used as
/// the format string.
init(key: String, formattedWith arguments: [CVarArg]) {…}
}
extension String {
init(_ localizedString: LocalizedString) {
self.init(describing: localizedString)
}
}
extension LocalizedString {
/// Initializes a LocalizedString with no arguments which uses the
indicated `key`. `%`
/// characters in the `key` will be converted to `%%`.
///
/// If the `key` does not exist in the localized string file, the `key`
itself will be used as
/// the string.
init(key: String) {…}
/// Initializes a LocalizedString to represent the indicated `value`.
init(_ value: LocalizedStringConvertible) {…}
/// Initializes a LocalizedString to represent the empty string.
init() {…}
}
extension LocalizedString: CustomStringConvertible {…}
extension LocalizedString: ExpressibleByStringLiteral {
init(stringLiteral value: String) {
self.init(key: value)
}
…
}
The current ExpressibleByStringInterpolation protocol has a number of defects.
1. We want to only permit LocalizedStringConvertible types, or at least
*use* the LocalizedStringConvertible conformance; neither of these appears to
be possible. (`is` and `as?` casts always fail, overloads don't seem to be
called, etc.)
2. The literal parts of the string are interpreted using `String`'s
`ExpressibleByStringLiteral` conformance; we really want them to use
`LocalizedString`'s instead.
3. We don't want the literal parts of the string to pass through
`init(stringInterpolationSegment:)`, because we want to treat interpolation and
literal segments differnetly.
In other words, we want to be able to write something like this:
extension LocalizedString: ExpressibleByStringInterpolation {
typealias StringInterpolatableType = LocalizedStringConvertible
init(stringInterpolation segments: LocalizedString) {
self.init()
for segment in segments {
formatKey += segment.formatKey
arguments += segment.arguments
}
}
init(stringInterpolationSegment expr:
LocalizedStringConvertible) {
self.init(expr)
}
}
And change the code generated by the compiler from (given the statement `"foo
\(bar) baz" as LocalizedString`) this:
LocalizedString(stringInterpolation:
LocalizedString(stringInterpolationSegment:
String(stringLiteral: "foo ")),
LocalizedString(stringInterpolationSegment: bar),
LocalizedString(stringInterpolationSegment:
String(stringLiteral: " baz"))
)
To this:
LocalizedString(stringInterpolation:
LocalizedString(stringLiteral: "foo "),
LocalizedString(stringInterpolationSegment: bar),
LocalizedString(stringLiteral: " baz")
)
This would obviously require a few changes to the
ExpressibleAsStringInterpolation protocol:
// You cannot accept interpolations unless you can also be a plain
literal.
// Necessary for literal segments.
protocol ExpressibleByStringInterpolation: ExpressibleByStringLiteral {
// An associated type for the type of a permitted interpolation
associatedtype StringInterpolatableType = Any
// No changes here
init(stringInterpolation segments: Self...)
// No longer generic; instead uses StringInterpolatableType
existentials.
// Also a semantic change: this is only called for the actual
interpolations.
// init(stringLiteral:) is called for literal segments.
init(stringInterpolationSegment expr: StringInterpolatableType)
// Given the change in roles, we might want to consider
renaming the initializers:
//
// init(stringInterpolation:) => init(combinedStringLiteral:)
or init(stringInterpolationSegments:)
// init(stringInterpolationSegment:) =>
init(stringInterpolation:)
}
Or perhaps we would hoist the combining initializer up into
ExpressibleAsStringLiteral, and generate an `init(combinedStringLiteral:)` call
every time string literals are used.
protocol ExpressibleByStringLiteral {
associatedtype StringLiteralType:
_ExpressibleByBuiltinStringLiteral = String
init(stringLiteralSegments segments: Self...)
init(stringLiteral value: StringLiteralType)
}
protocol ExpressibleByStringInterpolation: ExpressibleByStringLiteral {
associatedtype StringInterpolatableType = Any
init(stringInterpolation expr: StringInterpolatableType)
}
// "foo" as LocalizedString
LocalizedString(stringLiteralSegments:
LocalizedString(stringLiteral: "foo")
)
// "foo \(bar) baz" as LocalizedString
LocalizedString(stringInterpolation:
LocalizedString(stringLiteral: "foo "),
LocalizedString(stringInterpolation: bar),
LocalizedString(stringLiteral: " baz")
)
Now, it's quite possible--perhaps even likely--that there are really good
reasons for the current design. But I've been thinking about this for two years
and I don't know what they are yet; nor can I find much relevant design
documentation. I, too, would love to find out why the current design was
selected.
--
Brent Royal-Gordon
Architechies
_______________________________________________
swift-evolution mailing list
[email protected]
https://lists.swift.org/mailman/listinfo/swift-evolution