<div dir="ltr"><div class="gmail_extra"><div class="gmail_quote">On Sat, Mar 11, 2017 at 5:34 AM, Brent Royal-Gordon <span dir="ltr">&lt;<a href="mailto:brent@architechies.com" target="_blank">brent@architechies.com</a>&gt;</span> wrote:<br><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-m_-789333835782937103gmail-">&gt; On Mar 10, 2017, at 11:17 PM, Jacob Bandes-Storch &lt;<a href="mailto:jtbandes@gmail.com" target="_blank">jtbandes@gmail.com</a>&gt; wrote:<br>
&gt;<br>
&gt; I&#39;m confused by this example — was ExpressibleByFailableStringInt<wbr>erpolation&#39;s init() supposed to be failable here?<br>
<br>
</span>Ugh, yes, I&#39;m sorry. That should have been:<br>
<span class="gmail-m_-789333835782937103gmail-"><br>
        protocol ExpressibleByFailableStringInt<wbr>erpolation: ExpressibleByStringLiteral {<br>
                associatedtype StringInterpolationType<br>
<br>
</span>                init?(stringInterpolation: StringInterpolationSegment...)<br>
        }<br>
<br>
        ...<br>
<span class="gmail-m_-789333835782937103gmail-"><br>
        extension Optional: ExpressibleByStringInterpolati<wbr>on where Wrapped: ExpressibleByFailableStringInt<wbr>erpolation {<br>
                typealias StringLiteralType = Wrapped.StringLiteralType<br>
                typealias StringInterpolationType = Wrapped.StringInterpolationTyp<wbr>e<br>
<br>
                init(stringInterpolation segments: StringInterpolationSegment...) {<br>
                        self = Wrapped(stringInterpolation: segments)<br>
                }<br>
        }<br>
<br>
</span><span class="gmail-m_-789333835782937103gmail-">&gt; 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?<br>
&gt;<br>
&gt; protocol ExpressibleByStringInterpolati<wbr>on {<br>
&gt;     associatedtype Interpolation<br>
&gt;     init(_ string: String, with interpolations: (String.Index, Interpolation)...)<br>
&gt; }<br>
<br>
</span>I&#39;ve thought about that too. It&#39;s a little bit limiting—you have no choice except to use `String` as your input type. Also, the obvious way to use these parameters:<br>
<br>
        init(stringLiteral string: String, with interpolations: (String.Index, Interpolation)...) {<br>
                var copy = string<br>
                for (i, expr) in interpolations {<br>
                        let exprString = doSomething(with: expr)<br>
                        copy.insert(exprString, at: i)<br>
                }<br>
                self.string = copy<br>
        }<br>
<br>
Will probably be slow, since you&#39;re inserting into the middle instead of appending to the end. Obviously a clever programmer can avoid doing that, but why create the attractive nuisance in the first place?<br></blockquote><div><br></div><div>It&#39;s also easy to get wrong ;-)  Docs for insert(_:at:) say &quot;Calling this method invalidates any existing indices for use with this string.&quot; And even if they weren&#39;t invalidated, but simple numerical indices as into an Array, you&#39;d need to use them offsetBy however much content you&#39;d inserted so far.</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">
<span class="gmail-m_-789333835782937103gmail-"><br>
&gt; On the other hand, unless I&#39;ve missed something, it seems like most of the suggestions so far are assuming that for any ExpressibleByStringInterpolati<wbr>on type, the interpolated values&#39; types will be homogeneous. In the hypothetical printf-replacement case, you&#39;d really want the value types to depend on the format specifiers, so that a Float couldn&#39;t be passed to %d without explicitly converting to an integer type.<br>
&gt;<br>
&gt; Although I suppose that could simply be achieved with a typealias Interpolation = enum { case f(Float), d(Int), ... }<br>
<br>
<br>
</span>Yup. Given Swift&#39;s current feature set, the only way to design this is to have a single type which all interpolations funnel through. That type could be `Any`, of course, but that doesn&#39;t really help anybody.<br>
<br>
If we had variadic generics, you could of course have a variadic initializer with heterogeneous types. And I&#39;ve often given thought to a &quot;multiple associated types&quot; feature where a protocol could be conformed to multiple times by specifying more than one concrete type for specific associated types. But these are both exotic features. In their absence, an enum (or something like that) is probably the best choice.<br>
<br>
* * *<br>
<br>
I&#39;m going to try to explore some of these other designs, but they all seem to assume the new formatting system I sketched out in &quot;future directions&quot;, so I implemented that first:<br>
<br>
        <a href="https://github.com/brentdax/swift/compare/new-interpolation...brentdax:new-interpolation-formatting" rel="noreferrer" target="_blank">https://github.com/brentdax/sw<wbr>ift/compare/new-interpolation.<wbr>..brentdax:new-interpolation-<wbr>formatting</a><br>
<br>
The switch to `\(describing: foo)` has more impact than I expected; just the code that&#39;s built by `utils/build-script`—not including tests—has over a hundred lines with changes like this:<br>
<br>
-    expectationFailure(&quot;\(lhs) &lt; \(rhs)&quot;, trace: ${trace})<br>
+    expectationFailure(&quot;\(describi<wbr>ng: lhs) &lt; \(describing: rhs)&quot;, trace: ${trace})<br>
<br>
On the other hand, I like what it does to other formatting (I&#39;ve only applied this kind of change in a few places):<br>
<br>
-    return &quot;CollectionOfOne(\(String(refl<wbr>ecting: _element)))&quot;<br>
+    return &quot;CollectionOfOne(\(reflecting: _element))&quot;<br>
<br>
And it *does* make you think about whether you want to use `describing:` or `reflecting:`:<br>
<br>
-    expectEqual(expected, actual, &quot;where the argument is: \(a)&quot;)<br>
+    expectEqual(expected, actual, &quot;where the argument is: \(describing: a)&quot;)<br>
<br>
And, thanks to LosslessStringConvertible, it also does a pretty good job of calling out the difference between interpolations that will probably look good to a user and ones that will look a little funny:<br>
<br>
-      return &quot;watchOS(\(major).\(minor).[\(<wbr>bugFixRange)], reason: \(reason))&quot;<br>
+      return &quot;watchOS(\(major).\(minor).[\(<wbr>describing: bugFixRange)], reason: \(reason))&quot;<br>
<br>
All in all, it&#39;s a bit of a mixed bag:<br>
<br>
-          return &quot;&lt;\(type(of: x)): 0x\(String(asNumericValue(x), radix: 16, uppercase: false))&gt;&quot;<br>
+          return &quot;&lt;\(describing: type(of: x)): 0x\(asNumericValue(x), radix: 16, uppercase: false)&gt;&quot;<br>
<br>
We could probably improve this situation with a few targeted `String.init(_:)`s for things like type names, `Error` instances, and `FloatingPoint` types. (Actually, I think that `FloatingPoint` should probably conform to `LosslessStringConvertible`, but that&#39;s a different story.) Possibly `Array`s of `LosslessStringConvertible` types as well.<br>
<br>
But ultimately, this might just be too source-breaking. If it is, we&#39;ll need to think about changing the design.<br>
<br>
The simplest fix is to add a leading parameter label if there isn&#39;t one—that is, `\(foo)` becomes `.init(formatting: foo)`—but then you lose the ability to use full-width initializers which are already present and work well outside of interpolation. Perhaps we could hack overload checking so that, if a particular flag is set on a call, it will consider both methods with *and* without the first parameter label? But that&#39;s kind of bizarre, and probably above my pay grade to implement.<br>
<br>
In any case, I really think this is in the right general direction, and with it done, I can start exploring some of the alternatives we&#39;ve discussed here. I&#39;m hoping to build several and run the string interpolation benchmark against them—we&#39;ll see how that goes.<br>
<span class="gmail-m_-789333835782937103gmail-HOEnZb"><font color="#888888"><br>
--<br>
Brent Royal-Gordon<br>
Architechies<br>
<br>
</font></span></blockquote></div><br></div><div class="gmail_extra">I still have the feeling that using full-on argument labels is too wordy for string interpolation. The grammar is a bit awkward: &quot;a String describing x&quot; makes sense when reading String(describing: x), but &quot;\(describing: x)&quot; has no subject. And, at least for number formatting, it&#39;s nowhere near the concision of printf-style format specifiers. Although \() into T.init() does seem like a very obvious way of allowing more than just a single argument per interpolation — maybe what I long for is not a better core syntax, but a great DSL built on top of it. I&#39;m struggling to come up with anything that would be particularly ergonomic.</div><div class="gmail_extra"><br></div><div class="gmail_extra">enum FloatSpec { case pad(Character), width(Int), ... }</div><div class="gmail_extra">init&lt;F: FloatingPoint&gt;(_ value: F, _ specs: FloatSpec...)</div><div class="gmail_extra">&quot;\(value, .width(10), .pad(4))&quot;</div><div class="gmail_extra"><br></div><div class="gmail_extra"><br></div><div class="gmail_extra">However, your proposal as written is a great step!</div><div class="gmail_extra"><br></div></div>