[swift-evolution] [Draft] Fix ExpressibleByStringInterpolation
Joe Groff
jgroff at apple.com
Fri Mar 10 13:45:10 CST 2017
> On Mar 10, 2017, at 11:27 AM, David Waite <david at alkaline-solutions.com> wrote:
>
>>
>> On Mar 10, 2017, at 9:49 AM, Joe Groff via swift-evolution <swift-evolution at swift.org> wrote:
>>
>> Having ExpressibleByStringInterpolation refine ExpressibleByStringLiteral makes sense. I think there's a more powerful alternative design you should also consider. If the protocol looked like this:
>>
>> protocol ExpressibleByStringInterpolation: ExpressibleByStringLiteral {
>> associatedtype LiteralSegment: ExpressibleByStringLiteral
>> associatedtype InterpolatedSegment
>> init(forStringInterpolation: Void)
>>
>> mutating func append(literalSegment: LiteralSegment)
>> mutating func append(interpolatedSegment: InterpolatedSegment)
>> }
>>
>> Then an interpolation expression like this in `Thingy` type context:
>>
>> "foo \(bar) bas \(zim: 1, zang: 2)\n"
>>
>> could desugar to something like:
>>
>> {
>> var x = Thingy(forStringInterpolation: ())
>> // Literal segments get appended using append(literalSegment: "literal")
>> x.append(literalSegment: "foo ")
>> // \(...) segments are arguments to a InterpolatedSegment constructor
>> x.append(interpolatedSegment: Thingy.InterpolatedSegment(bar))
>> x.append(literalSegment: " bas ")
>> x.append(interpolatedSegment: Thingy.InterpolatedSegment(zim: 1, zang: 2))
>>
>> return x
>> }()
>>
>> This design should be more efficient, since there's no temporary array of segments that needs to be formed for a variadic argument, you don't need to homogenize everything to Self type up front, and the string can be built up in-place. It also provides means to address problems 3 and 4, since the InterpolatedSegment associated type can control what types it's initializable from, and can provide initializers with additional arguments for formatting or other purposes.
>
> Hi Joe,
>
> The trade-offs for this approach would be:
> - each append would need to return a valid object w.r.t the type’s invariants.
> - an implementation could use the init(stringInterpolation:) could be a final building step, while append would not indicate that the object construction was complete.
> One example where this could be a problem would be if someone used the segments to build up a localized representation of the interpolated string.
Validation is a general problem with the literal protocols, since none of the literal protocols allow for failed initialization, and if you can write "foo \(bar) bas", you can write "foo " or "foo \(bar)", so you need to have a representation for those intermediate states already. I think allowing the literal and interpolated types to be different is important. You could achieve that with an initializer that took a variadic list of enums, perhaps:
protocol ExpressibleByStringInterpolation: ExpressibleByStringLiteral {
associatedtype LiteralSegment: ExpressibleByStringLiteral
associatedtype InterpolatedSegment
enum Segment { case literal(LiteralSegment), interpolated(InterpolatedSegment) }
init(stringInterpolation: Segment...)
}
That still requires the argument array to be constructed up front, though.
-Joe
More information about the swift-evolution
mailing list