[swift-evolution] Default Generic Arguments

Xiaodi Wu xiaodi.wu at gmail.com
Tue Jan 24 17:33:19 CST 2017


As I replied above, this doesn't work IMO because omitted generic arguments
are inferred, and that can't change without being hugely source-breaking.

I think it's absolutely essential that adding a default to my library
doesn't change the behavior of code that uses my library. That's currently
the case, afaict, for all default arguments, and so I think it's essential
here.


On Tue, Jan 24, 2017 at 17:26 Srđan Rašić via swift-evolution <
swift-evolution at swift.org> wrote:

> We are probably taking the wrong direction here and trying to solve the
> problem that does not need solving. We are discussing how to infer
> gereneric arguments in type declarations while we should not do that at
> all.
>
> Let me repeat Doug's examples:
>
>
> struct X<T = Int> { }
>
> func f1() -> X<Double> { return X() }
>
> func f2() -> X<Int> { return X() }
> func f2() -> X<Double> { return X() }
>
> func f3<T>(_: T) -> X<T> { return X() }
>
> let x1: X = f1()   // okay: x1 has type X<Double>?
> let x2: X = f2()   // ambiguous?
> let x3a: X = f3(1.5)   // okay: x3a has type X<Double>?
> let x3b: X = f3(1)   // okay: x3a has type X<Int>?
>
> Thinking about what the generic argument of X should be inferred to for
> x1, x2 and x3 is pointless. If one omits generic arguments in the variable
> declaration, one is accepting the defaults. In other words, doing let x: X
> = ... should always be treated as doing let x: X<Int> = ..., regardless of
> what we have on the right hand side. No inference should happen in this
> case. It would mean inferring already specified type.
>
> Why? Consider what happens if we define x as a property:
>
> struct Test {
>   let x: X
>
>   init() {
>     x = f()
>   }
> }
>
> It would make no sense that the initialization in the initializer
> specializes the generic argument of the property, so for the sake of
> consistency we should not do it for the variables/constants either.
>
> Given that, we can solve Doug's example as:
>
> let x1: X = f1() // error: cannot assign X<Double> to X<Int>
>
> let x2: X = f2()   // ok: using X<Int> overload
> let x3a: X = f3(1.5)   // error like in x1
> let x3b: X = f3(1)   // ok because rhs is inferred as X<Int>
>
> I think this is the only valid way to go and it really simplifies things,
> both the understanding of how the feature works, but also the
> implementation.
>
> What do you think?
>
>
> tir. 24. jan. 2017 kl. 22.16 skrev David Sweeris <davesweeris at mac.com>:
>
>
> On Jan 24, 2017, at 11:41, Alexis via swift-evolution <
> swift-evolution at swift.org> wrote:
>
> It’s worth noting that the question of “how do these defaults interact
> with other defaults” is an issue that has left this feature dead in the
> water in the Rust language despite being accepted for inclusion two years
> ago. See
> https://internals.rust-lang.org/t/interaction-of-user-defined-and-integral-fallbacks-with-inference/2496 for
> some discussion of the issues at hand.
>
> For those who don’t want to click that link, or are having trouble
> translating the syntax/terms to Swift. The heart of Niko’s post is the
> following (note: functions are used here for expedience; you can imagine
> these are `inits` for a generic type if you wish):
>
> // Example 1: user supplied default is IntegerLiteralConvertible
>
> func foo<T=Int64>(t: T) { ... }
>
> foo<_>(22)
> //  ^
> //  |
> //  What type gets inferred here?
>
>
>
> // Example 2: user supplied default isn't IntegerLiteralConvertible
>
> func bar<T=Character>(t: T) { ... }
>
> bar<_>(22)
> //  ^
> //  |
> //  What type gets inferred here?
>
>
> There are 4 strategies:
>
> (Note: I use “integer literal” here for simplicity; in general it's “any
> kind of literal, and its associated LiteralType”. So this reasoning also
> applies to FloatLiteralType, StringLiteralType, BooleanLiteralType, etc.)
>
> * Unify all: always unify the variables with all defaults. This is the
> conservative choice in that it gives an error if there is any doubt.
>
> * Prefer literal: always prefer IntegerLiteralType (Int). This is the
> maximally backwards compatible choice, but I think it leads to very
> surprising outcomes.
>
> * Prefer user: always the user-defined choice. This is simple from one
> point of view, but does lead to a potentially counterintuitive result for
> example 2.
>
> * Do What I Mean (DWIM): Prefer the user-defined default, except in the
> case where the variable is unified with an integer literal *and* the
> user-defined default isn't IntegerLiteralConvertible. This is complex to
> say but leads to sensible results on both examples. (Basically: prefer
> user, but fallback to IntegerLiteralType if the user default doesn’t
> actually make sense)
>
> | Strategy       | Example 1 | Example 2 |
>
> | -------------- | --------- | --------- |
>
> | Unify all      | Error     | Error     |
>
> | Prefer literal | Int       | Int       |
>
> | Prefer user    | Int64     | Error     |
>
> | DWIM           | Int64     | Int       |
>
>
> Personally, I’ve always favoured DWIM. Especially in Swift where
> IntegerLiteralType inference is so frequently used (you don’t want adding a
> default to cause code to stop compiling!). In practice I don’t expect there
> to be many cases where this ambiguity actually kicks in, as it requires the
> user-specified default to be a LiteralConvertible type that isn't the
> relevant LiteralType, and for the type variable to affect an actual
> Literal. So <T=String>(x: T) never causes problems, but <T=StaticString>(x:
> T) does.
>
> As for the matter of “what if I want the other one” — you clearly know the
> actual type you want; just name it explicitly.
>
>
> We could just remove that parameter from the type inference system... if
> the default value is Int and the user passes in a String, that'd be an
> error, unless the user also sets that parameter to String.
>
> I'd envisioned using default parameters as more "compile-time
> configuration options" than something for the type system to actually have
> deal with. IIRC, I was trying to come up with a way to write just one
> arbitrary-sized integer struct where I *could* specify the width of its
> internal calculations, but would usually just use a default value.
>
> - Dave Sweeris
>
> _______________________________________________
> swift-evolution mailing list
> swift-evolution at swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20170124/65ec37fc/attachment.html>


More information about the swift-evolution mailing list