[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