[swift-evolution] ExpressibleByStringInterpolation vs. String re-evaluation vs. Regex

Jacob Bandes-Storch jtbandes at gmail.com
Wed Aug 3 22:40:31 CDT 2016


Here's another example use case: Auto Layout visual format strings.

https://gist.github.com/jtbandes/9c1c25ee4996d2554375#file-constraintcollection-swift-L85-L87

Since only views and numeric types are supported, the generic init<T> has
to be a run-time error. Ideally it could be a compile-time error.

On Tue, Aug 2, 2016 at 6:10 PM, Brent Royal-Gordon <brent at architechies.com>
wrote:

> > On Jul 30, 2016, at 10:35 PM, Jacob Bandes-Storch via swift-evolution <
> swift-evolution at swift.org> 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.
>

Yep, this is what I filed https://bugs.swift.org/browse/SR-1260 for.


>
> 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
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20160803/2fe56ec8/attachment.html>


More information about the swift-evolution mailing list