[swift-dev] Advice for implementing "literal values as generic types"

David Sweeris davesweeris at mac.com
Wed Aug 30 12:18:53 CDT 2017

> On Aug 30, 2017, at 8:15 AM, Tino Heth <2th at gmx.de> wrote:
> That's a great choice ;-) — if I had the time, this would be the Swift-feature I'd spend it for…

Yeah, I remember your enthusiasm for it back when the topic first came up :-)

> I hope you have the stamina to finish that

lol, me too!

> and although it's no real help for implementation, I have some remarks that might be useful in the long run.
> There are already some thoughts on the topic online (afair even core had something to say about it — I can try to find it if you didn't do so already ;-).
> Your Vector example illustrates one problem: "T: X" can now have two different meanings; either T is a subtype of X, or T is a literal of type X.
> Even if the compiler can figure out what meaning to chose, it would help humans to have a slightly different syntax.

Maybe, yeah... I'm trying to make it so that the literal value itself is what's conforming to X (which is why this feature is only about literal values, and not arbitrary values of a type that conforms to the relevant `ExpressibleBy*Literal` protocol), to preserve the "untyped" nature of literal values. This would let us simply use the "type" by itself (like in the init body from the original Vector example) wherever an integer literal would work. How such a protocol would be implemented is something I haven't worked on yet, but I'll probably start by taking a look at how the existing `ExpressibleBy*Literal` protocols work. Fully implementing this (by which I mean allowing for adding generic literals like in the "join" function) would require some way of having the compiler track which identifiers can be evaluated at compile-time and which are strictly run-time objects, which is why I think this is so closely tied to what C++ calls "constexpr".

Failing that, plan B is to write a protocol something like: 
protocol IntegerLiteralExpr {
  static var value: IntegerLiteralType {get}
which would change the init function in my example to;
  init() {
    elements = [T](repeating: 0, count: L.value)

> There's also the idea of labeled generic parameters, and default values for them.
> Both aren't required, but imho at least the labels might be something that people request before another kind of generics is accepted.

I might start on those next, if nobody else has done it before I get this feature working. No promises, though... not until I've gone through the process once and have a better idea of what I'm getting into.

>> struct Vector<T: ExpressibleByIntegerLiteral, L: IntegerLiteralExpr> {
>>   var elements: [T]
>>   init() {
>>     elements = [T](repeating: 0, count: L)
>>   }
>> }
>> let vect = Vector<Int, 5>()
> I have strong hope that literal generics will form the basis for fixed-size vectors, so that there will be no need to define such a type ;-) — but besides that:
> Why is T restricted here? Even if it should be a numeric type, I would allow floats as well.

Floating point types do conform to `ExpressibleByIntegerLiteral`. Unless you meant to ask why `L` was restricted, in which case it's simply that Vector/Matrix types are my main motivation, so I started with integers. My intention is to implement it in such a way that any literal value will work, even, say, a hypothetical "ClosureLiteralExpr", "let x = Foo<let bar = whatever(something)>()". Anyway, the point is that I very much don't want to tie this feature's implementation to the existing set of literal expressions. As a more likely example, given how the evolution discussions were going, it seems reasonable to think it's possible we might get a RegExLiteral some day. IIUC (which is far from certain), in the worst case, I could just store the relevant characters of source code. Come to think of it, I might want to do that even for integer literals, if that makes it easier to preserve their "literalness" in the type system, but I'll cross that bridge when I come to it.

>> And, once that's working, I'm going to add support simple "type functions":
>> func join <T, L1, L2> (_ lhs: Vector<T, L1>, _ rhs: Vector<T, L2>) -> Vector<T, L1 + L2 > {...}
>> I think restricting the supported "type functions" to expressions that could be evaluated by the compiler's "constant folding" code would be a reasonable place to start, until we figure out what we want to do about "pure"/"constexpr" stuff... even just "+" for numeric and string literals, and "-" for numeric literals, seems like a reasonable starting goal, and I think that'd be simple enough to implement (famous last words, right?)... It's all academic until I get the simple cases working first, though.
> Even without such calculations, the feature would be quite useful:
> For type-safe matrices, it would already be enough to have the dimensions attached to the type.

Agreed, but it'd be a pretty severe limitation, IMHO, to not be able to split/join values of such types.

- Dave Sweeris

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-dev/attachments/20170830/379ca9c2/attachment.html>

More information about the swift-dev mailing list