> On Mar 10, 2017, at 11:17 PM, Jacob Bandes-Storch <[email protected]> wrote:
> 
> I'm confused by this example — was ExpressibleByFailableStringInterpolation's 
> init() supposed to be failable here?

Ugh, yes, I'm sorry. That should have been:

        protocol ExpressibleByFailableStringInterpolation: 
ExpressibleByStringLiteral {
                associatedtype StringInterpolationType
                
                init?(stringInterpolation: StringInterpolationSegment...)
        }

        ...
        
        extension Optional: ExpressibleByStringInterpolation where Wrapped: 
ExpressibleByFailableStringInterpolation {
                typealias StringLiteralType = Wrapped.StringLiteralType
                typealias StringInterpolationType = 
Wrapped.StringInterpolationType
                
                init(stringInterpolation segments: 
StringInterpolationSegment...) {
                        self = Wrapped(stringInterpolation: segments)
                }
        }

> 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?
> 
> protocol ExpressibleByStringInterpolation {
>     associatedtype Interpolation
>     init(_ string: String, with interpolations: (String.Index, 
> Interpolation)...)
> }

I've thought about that too. It'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:

        init(stringLiteral string: String, with interpolations: (String.Index, 
Interpolation)...) {
                var copy = string
                for (i, expr) in interpolations {
                        let exprString = doSomething(with: expr)
                        copy.insert(exprString, at: i)
                }
                self.string = copy
        }

Will probably be slow, since you'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?

> 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.
> 
> Although I suppose that could simply be achieved with a typealias 
> Interpolation = enum { case f(Float), d(Int), ... }


Yup. Given Swift'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't really help anybody.

If we had variadic generics, you could of course have a variadic initializer 
with heterogeneous types. And I've often given thought to a "multiple 
associated types" 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.

* * *

I'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 "future directions", so I 
implemented that first:

        
https://github.com/brentdax/swift/compare/new-interpolation...brentdax:new-interpolation-formatting

The switch to `\(describing: foo)` has more impact than I expected; just the 
code that's built by `utils/build-script`—not including tests—has over a 
hundred lines with changes like this:

-    expectationFailure("\(lhs) < \(rhs)", trace: ${trace})
+    expectationFailure("\(describing: lhs) < \(describing: rhs)", trace: 
${trace})

On the other hand, I like what it does to other formatting (I've only applied 
this kind of change in a few places):

-    return "CollectionOfOne(\(String(reflecting: _element)))"
+    return "CollectionOfOne(\(reflecting: _element))"

And it *does* make you think about whether you want to use `describing:` or 
`reflecting:`:

-    expectEqual(expected, actual, "where the argument is: \(a)")
+    expectEqual(expected, actual, "where the argument is: \(describing: a)")

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:

-      return "watchOS(\(major).\(minor).[\(bugFixRange)], reason: \(reason))"
+      return "watchOS(\(major).\(minor).[\(describing: bugFixRange)], reason: 
\(reason))"

All in all, it's a bit of a mixed bag:

-          return "<\(type(of: x)): 0x\(String(asNumericValue(x), radix: 16, 
uppercase: false))>"
+          return "<\(describing: type(of: x)): 0x\(asNumericValue(x), radix: 
16, uppercase: false)>"

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's a different story.) Possibly `Array`s 
of `LosslessStringConvertible` types as well.

But ultimately, this might just be too source-breaking. If it is, we'll need to 
think about changing the design.

The simplest fix is to add a leading parameter label if there isn'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's kind of bizarre, and probably above my pay 
grade to implement.

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've discussed here. I'm 
hoping to build several and run the string interpolation benchmark against 
them—we'll see how that goes.

-- 
Brent Royal-Gordon
Architechies

_______________________________________________
swift-evolution mailing list
[email protected]
https://lists.swift.org/mailman/listinfo/swift-evolution

Reply via email to