> 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

Reply via email to