<div dir="ltr"><div class="gmail_extra"><div><div class="gmail_signature"><div dir="ltr"><div>On Fri, Mar 10, 2017 at 4:44 PM, Brent Royal-Gordon via swift-evolution <span dir="ltr"><<a href="mailto:swift-evolution@swift.org" target="_blank">swift-evolution@swift.org</a>></span> wrote:<br></div></div></div></div><div class="gmail_quote"><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex"><span class="gmail-">> On Mar 10, 2017, at 8:49 AM, Joe Groff <<a href="mailto:jgroff@apple.com">jgroff@apple.com</a>> wrote:<br>
><br>
> I think there's a more powerful alternative design you should also consider. If the protocol looked like this:<br>
><br>
> protocol ExpressibleByStringInterpolati<wbr>on: ExpressibleByStringLiteral {<br>
> associatedtype LiteralSegment: ExpressibleByStringLiteral<br>
> associatedtype InterpolatedSegment<br>
> init(forStringInterpolation: Void)<br>
><br>
> mutating func append(literalSegment: LiteralSegment)<br>
> mutating func append(interpolatedSegment: InterpolatedSegment)<br>
> }<br>
><br>
> Then an interpolation expression like this in `Thingy` type context:<br>
><br>
> "foo \(bar) bas \(zim: 1, zang: 2)\n"<br>
><br>
> could desugar to something like:<br>
><br>
> {<br>
> var x = Thingy(forStringInterpolation: ())<br>
> // Literal segments get appended using append(literalSegment: "literal")<br>
> x.append(literalSegment: "foo ")<br>
> // \(...) segments are arguments to a InterpolatedSegment constructor<br>
> x.append(interpolatedSegment: Thingy.InterpolatedSegment(<wbr>bar))<br>
> x.append(literalSegment: " bas ")<br>
> x.append(interpolatedSegment: Thingy.InterpolatedSegment(<wbr>zim: 1, zang: 2))<br>
><br>
> return x<br>
> }()<br>
><br>
> 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.<br>
<br>
</span>On the other hand, you end up with an `init(forStringInterpolation: ())` initializer which is explicitly intended to return an incompletely initialized instance. I don't enjoy imagining this. For instance, you might find yourself having to change certain properties from `let` to `var` so that the `append` methods can operate.<br>
<br>
If we *do* go this direction, though, I might suggest a slightly different design which uses fewer calls and makes the finalization explicit:<br>
<br>
protocol ExpressibleByStringLiteral {<br>
associatedtype StringLiteralSegment: ExpressibleByStringLiteral<br>
<br>
init(startingStringLiteral: ())<br>
func endStringLiteral(with segment: StringLiteralSegment)<br>
}<br>
protocol ExpressibleByStringInterpolati<wbr>on: ExpressibleByStringLiteral {<br>
associatedtype StringInterpolationSegment<br>
<br>
func continueStringLiteral(with literal: StringLiteralSegment, followedBy interpolation: StringInterpolationSegment)<br>
}<br>
<br>
Your `"foo \(bar) bas \(zim: 1, zang: 2)\n"` example would then become:<br>
<br>
{<br>
var x = Thingy(startingStringLiteral: ())<br>
x.continueStringLiteral(with: "Foo ", followedBy: .init(bar))<br>
x.continueStringLiteral(with: " bas ", followedBy: .init(zim: 1, zang: 2))<br>
x.endStringLiteral(with: "\n")<br>
return x<br>
}<br>
<br>
While a plain old string literal would have a more complicated pattern than they do currently, but one which would have completely compatible semantics with an interpolated string:<br>
<br>
{<br>
var x = Thingy(startingStringLiteral: ())<br>
x.endStringLiteral(with: "Hello, world!")<br>
return x<br>
}<br>
<br>
* * *<br>
<br>
Another possible design would separate the intermediate type from the final one. For instance, suppose we had:<br>
<br>
protocol ExpressibleByStringInterpolati<wbr>on: ExpressibleByStringLiteral {<br>
associatedtype StringInterpolationBuffer = Self<br>
associatedtype StringInterpolationType<br>
<br>
static func makeStringLiteralBuffer(<wbr>startingWith firstLiteralSegment: StringLiteralType) -> StringLiteralBuffer<br>
static func appendInterpolationSegment(_ expr: StringInterpolationType, to stringLiteralBuffer: inout StringLiteralBuffer)<br>
static func appendLiteralSegment(_ string: StringLiteralType, to stringLiteralBuffer: inout StringLiteralBuffer)<br>
<br>
init(stringInterpolation buffer: StringInterpolationBuffer)<br>
}<br>
// Automatically provide a parent protocol conformance<br>
extension ExpressibleByStringInterpolati<wbr>on {<br>
init(stringLiteral: StringLiteralType) {<br>
let buffer = Self.makeStringLiteralBuffer(<wbr>startingWith: stringLiteral)<br>
self.init(stringInterpolation: buffer)<br>
}<br>
}<br>
<br>
Then your example would be:<br>
<br>
{<br>
var buffer = Thingy.<wbr>makeStringLiteralBuffer(<wbr>startingWith: "foo ")<br>
Thingy.<wbr>appendInterpolationSegment(<wbr>Thingy.<wbr>StringInterpolationSegment(<wbr>bar), to: &buffer)<br>
Thingy.appendLiteralSegment(" bas ", to: &buffer)<br>
Thingy.<wbr>appendInterpolationSegment(<wbr>Thingy.<wbr>StringInterpolationSegment(<wbr>zim: 1, zang: 2), to: &buffer)<br>
Thingy.appendLiteralSegment("\<wbr>n", to: &buffer)<br>
<br>
return Thingy(stringInterpolation: x)<br>
}()<br>
<br>
For arbitrary string types, `StringInterpolationBuffer` would probably be `Self`, but if you had something which could only create an instance of itself once the entire literal was gathered together, it could use `String` or `Array` or whatever else it wanted.<br>
<br>
* * *<br>
<br>
One more design possibility. Would it make sense to handle all the segments in a single initializer call, instead of having one call for each segment, plus a big call at the end? Suppose we did this:<br>
<br>
protocol ExpressibleByStringInterpolati<wbr>on: ExpressibleByStringLiteral {<br>
associatedtype StringInterpolationType<br>
<br>
init(stringInterpolation segments: StringInterpolationSegment...)<br>
}<br>
@fixed_layout enum StringInterpolationSegment<<wbr>StringType: ExpressibleByStringInterpolati<wbr>on> {<br>
case literal(StringType.<wbr>StringLiteralType)<br>
case interpolation(StringType.<wbr>StringInterpolationType)<br>
}<br>
extension ExpressibleByStringInterpolati<wbr>on {<br>
typealias StringInterpolationSegment = Swift.<wbr>StringInterpolationSegment<<wbr>Self><br>
<br>
init(stringLiteral: StringLiteralType) {<br>
self.init(stringInterpolation: .literal(stringLiteral))<br>
}<br>
}<br>
<br>
Code pattern would look like this:<br>
<br>
Thingy(stringInterpolation:<br>
.literal("Foo "),<br>
.interpolation(.init(bar)),<br>
.literal(" bas "),<br>
.interpolation(.init(zim: 1, zang: 2)),<br>
.literal("\n")<br>
)<br>
<br>
I suppose it just depends on whether the array or the extra calls are more costly. (Well, it also depends on whether we want to be expanding single expressions into big, complex, multi-statement messes like we discussed before.)<br>
<br>
(Actually, I realized after writing this that you mentioned a similar design downthread. Oops.)<br></blockquote><div><br></div><div>This seems friendlier to me than the other designs. Not that it's a huge deal, but passing the segments one at a time introduces an unnecessary requirement that the construction happen left-to-right.</div><div> </div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">
<br>
* * *<br>
<br>
As for validation, which is mentioned downthread: I think we will really want plain old string literal validation to happen at compile time. Doing that in a general way means macros, so that's just not in the cards yet.<br>
<br>
However, once we *do* have that, I think we can actually handle runtime-failable interpolated literals pretty easily. For this example, I'll assume we adopt the `StringInterpolationSegment`-<wbr>enum-based option, but any of them could be adapted in the same way:<br>
<br>
protocol ExpressibleByFailableStringInt<wbr>erpolation: ExpressibleByStringLiteral {<br>
associatedtype StringInterpolationType<br>
<br>
init(stringInterpolation: StringInterpolationSegment...)<br>
}<br>
extension ExpressibleByFailableStringInt<wbr>erpolation {<br>
typealias StringInterpolationSegment = Swift.<wbr>StringInterpolationSegment<<wbr>Self?><br>
<br>
init(stringLiteral: StringLiteralType) {<br>
self.init(stringInterpolation segments: .literal(stringLiteral))<br>
}<br>
}<br>
extension Optional: ExpressibleByStringInterpolati<wbr>on where Wrapped: ExpressibleByFailableStringInt<wbr>erpolation {<br>
typealias StringLiteralType = Wrapped.StringLiteralType<br>
typealias StringInterpolationType = Wrapped.<wbr>StringInterpolationType<br>
<br>
init(stringInterpolation segments: StringInterpolationSegment...) {<br>
self = Wrapped(stringInterpolation: segments)<br>
}<br>
}<br>
<br>
If we think we'd rather support throwing inits instead of failable inits, that could be supported directly by `<wbr>ExpressibleByStringInterpolati<wbr>on` if we get throwing types and support `Never` as a "doesn't throw" type.<br></blockquote><div><br></div><div>I'm confused by this example — was ExpressibleByFailableStringInterpolation's init() supposed to be failable here?</div><div><br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">
<br>
* * *<br>
<br>
Related question: Can the construction of the variadic parameter array be optimized? For instance, the arity of any given call site is known at compile time; can the array buffer be allocated on the stack and somehow marked so that attempting to retain it (while copying the `Array` instance) will copy it into the heap? (Are we doing that already?) I suspect that would make variadic calls a lot cheaper, perhaps enough so that we just don't need to worry about this problem at all.<br>
<div class="gmail-HOEnZb"><div class="gmail-h5"><br>
--<br>
Brent Royal-Gordon<br>
Architechies<br>
<br>
______________________________<wbr>_________________<br>
swift-evolution mailing list<br>
<a href="mailto:swift-evolution@swift.org">swift-evolution@swift.org</a><br>
<a href="https://lists.swift.org/mailman/listinfo/swift-evolution" rel="noreferrer" target="_blank">https://lists.swift.org/<wbr>mailman/listinfo/swift-<wbr>evolution</a><br>
</div></div></blockquote></div><br></div><div class="gmail_extra">Just to throw out another idea, what about keeping the entirety of the string in one contiguous block and providing String.Indexes to the initializer?</div><div class="gmail_extra"><br></div><div class="gmail_extra">protocol ExpressibleByStringInterpolation {</div><div class="gmail_extra"> associatedtype Interpolation</div><div class="gmail_extra"> init(_ string: String, with interpolations: (String.Index, Interpolation)...)</div><div class="gmail_extra">}</div><div class="gmail_extra"><br></div><div class="gmail_extra"><br></div><div class="gmail_extra">On the other hand, unless I've missed something, it seems like most of the suggestions so far are assuming that for any ExpressibleByStringInterpolation type, the interpolated values' types will be homogeneous. In the hypothetical printf-replacement case, you'd really want the value types to depend on the format specifiers, so that a Float couldn't be passed to %d without explicitly converting to an integer type.</div><div class="gmail_extra"><br></div><div class="gmail_extra">Although I suppose that could simply be achieved with a typealias Interpolation = enum { case f(Float), d(Int), ... }</div></div>