[swift-evolution] [Proposal] Random Unification

Xiaodi Wu xiaodi.wu at gmail.com
Thu Nov 30 18:11:43 CST 2017


On Thu, Nov 30, 2017 at 5:29 PM, Jonathan Hull <jhull at gbis.com> wrote:

>
> On Nov 30, 2017, at 2:30 PM, Xiaodi Wu <xiaodi.wu at gmail.com> wrote:
>
> On Thu, Nov 30, 2017 at 3:58 PM, Dave DeLong via swift-evolution <swift-
> evolution at swift.org> wrote:
>
>>
>>
>> On Nov 30, 2017, at 2:48 PM, Jonathan Hull via swift-evolution <
>> swift-evolution at swift.org> wrote:
>>
>> I would personally go with:
>>
>> Int.random //Returns a random Int
>>
>>
>> “Type.random” is so rarely used as to not be worth the addition, IMO. If
>> you really need a random element from the *entire* domain, then I think you
>> should have to manually create the ClosedRange<T> yourself.
>>
>> Int.random(in: ClosedRange<Int>) //Works for Comparable types. Gives a
>> result from the closed range. Closed Range is never empty.
>>
>>
>> This is redundant. In order to pick a random element, you’re saying I
>> should have to do “Int.random(0 ..< 10)”? The redundancy here is that I
>> have to specify Int twice: once for the “.random” call, and again for the
>> type of the range. We can do better than that.
>>
>> [0,2,3].randomElement //Returns a random element from the collection
>>
>>
>> I strongly believe this should be a method, not a property. Properties,
>> like .first and .last, are expected to return the same value each time you
>> access them. “.random” inherently breaks that.
>>
>
> FWIW--and this isn't a vote, I know--I largely agree with Dave DeLong's
> conclusions above, and for substantially the same reasons.
>
>>
>> Then a version of each with a ‘using:’ parameter which takes a
>> generator/source:
>>
>> Int.random(using: RandomSource) //Returns a random Int using the given
>> source of randomness
>> Int.random(in: ClosedRange<Int>, using: RandomSource)
>> [0,2,3].randomElement(using: RandomSource)
>>
>> In my own RandomSource & RandomSourceCreatable protocols, I frequently
>> use random colors and sizes as well.  The issue there is that you really
>> want a closed range for each dimension. I wish Swift had a better notion of
>> dimensionality baked into the language.
>>
>> What I ended up doing was having a “constraints” parameter which took an
>> array of constraints which corresponded to various dimensions.  It works
>> for me, but it might be a bit complex for something in the standard library.
>>
>> Honestly, given the current capabilities of Swift what this really calls
>> for is custom initializers/functions for dimensional types:
>>
>> UIColor.random //This comes from the protocol
>> UIColor.random(hue: ClosedRange<CGFloat> = 0…1, saturation:
>> ClosedRange<CGFloat> = 0…1, brightness: ClosedRange<CGFloat> = 0…1, alpha:
>> ClosedRange<CGFloat> = 1…1)
>> //…and of course the same as above, but with ‘using:'
>>
>> Then you can easily get random colors which look like they belong
>> together:
>> let myColor = UIColor.random(saturation: 0.2…0.2, brightness: 0.6…0.6)
>>
>> There would probably also be a convenience version taking CGFloats and
>> passing them to the real function as ranges:
>>
>> let myColor = UIColor.random(saturation: 0.2, brightness: 0.6)
>>
>>
>> This means that our default RandomSource needs to be publicly available,
>> so that the custom functions can use it as the default…
>>
>>
> It does not. Having actually implemented some version of these APIs, it's
> readily apparent now to me that all custom types can simply call
> Int.random(in:) (or UnsafeRawBufferPointer<T>.random(byteCount:), or
> whatever else we want to have in the standard library) to get random values
> from the default RNG for any built-in type and size. The actual default
> random need never be exposed publicly, and since its functions are strictly
> redundant to these other APIs (which, of course, are the "currency" APIs
> that our purpose here is to design and make public), the default random is
> required only for internal implementation of the "currency" APIs and (a) is
> better off *not* exposed; (b) doesn't need to be of the same type as other
> RNGs, conform to the same protocols, or for that matter, does not even need
> to be a type or be written in Swift.
>
>
> I have also implemented some version of these APIs, both for Random and
> RepeatablyRandom sources.
>
> I get what you are saying about just being able to use the constructs from
> Int, etc…, but we still need a public default.  Let me give a concrete
> example of CGSize.  Yes, we want to just use CGFloat’s random, but if we
> don’t have a publicly available way to call the default then we have to
> implement the same algorithm twice, which is problematic. (Code written in
> Mail)
>
> static func random(width widthRange: ClosedRange<CGFloat>, height
> heightRange: ClosedRange<CGFloat>, using source: RandomSource = .default)
> -> CGSize {
> let w = CGFloat.random(in: widthRange, using: source)
> let h = CGFloat.random(in: heightRange, using: source)
> return CGSize(width: w, height: h)
> }
>
> Without the default I would have to have a second version which used
> CGFloat(in:) in order to use the default source/generator, which means I
> would have to update both places when I make changes.  Much better to just
> allow a default value for ‘using:'.
>

Ah, this is assuming that one thinks it is a good idea to have `using:`
variants. Firstly, I'm not entirely sold on them. But let's suppose you've
convinced me. The example above illustrates a huge footgun:

Why do people want to use a different RNG? A very common reason: to obtain
a different distribution of random values. Now, can you simply perform
memberwise initialization as you show above and obtain a final instance of
the desired distribution simply because the stored properties are drawn
from that distribution? No no no no no! This implementation is incorrect.

Not only is it not _problematic_ to require two distinct implementations,
it's almost certainly required if you want to have any hope of implementing
them correctly.

>
> 👍
>>
>>
>> Thanks,
>> Jon
>>
>>
>> On Nov 27, 2017, at 10:14 AM, TellowKrinkle via swift-evolution <
>> swift-evolution at swift.org> wrote:
>>
>> You say that all the `.random`s have different semantics, but to me (at
>> least), they are all very similar.  All the methods can be summarized as
>> selecting a single random element from a collection
>> `[0, 2, 3].random` selects a single element from the given collection
>> `Int.random(in: 0…8)` selects a single element from the given range
>> `Int.random` has no range, but selects a single element from the
>> collection of all ints (equivalent to if the above method had a default
>> value for its range)
>> So to me these are all doing the same operation, just with different
>> types of inputs
>>
>> 2017/11/24 20:07、Alejandro Alonso <aalonso128 at outlook.com>のメール:
>>
>>
>> - Alejandro
>>
>> ---------- Forwarded message ----------
>> *From:* Xiaodi Wu <xiaodi.wu at gmail.com>
>> *Date:* Nov 24, 2017, 3:05 PM -0600
>> *To:* Alejandro Alonso <aalonso128 at outlook.com>
>> *Cc:* Brent Royal-Gordon <brent at architechies.com>, Steve Canon via
>> swift-evolution <swift-evolution at swift.org>
>> *Subject:* Re: [swift-evolution] [Proposal] Random Unification
>>
>> On Fri, Nov 24, 2017 at 2:55 PM, Alejandro Alonso <aalonso128 at outlook.com
>> > wrote:
>>
>>> Regarding naming too many things “random”, I’ve talked to many
>>> developers on my end and they all don’t find it confusing. This proposal is
>>> aimed to make it obvious what the operation is doing when regarding random.
>>> I still agree that the proposed solution does just that and in practice
>>> feels good to write.
>>>
>>
>> I must disagree quite strongly here. The various facilities you name
>> "random" have different semantics, and differences in semantics should be
>> reflected in differences in names. It doesn't matter that some people don't
>> find it confusing; it is objectively the case that you have named multiple
>> distinct facilities with the same name, which leads to confusion. I, for
>> one, get confused, and you can see on this list that people are using
>> arguments about one property named "random" to discuss another property
>> named "random". This is quite an intolerable situation.
>>
>> I disagree that sample is the correct naming to use here. Getting a
>>> sample is a verb in this context which would make it break API guidelines
>>> just as well as `pick()`. To sample is to “take a sample or samples of
>>> (something) for analysis.” I can agree to use `sampling()` which follows
>>> API guidelines. This would result in the following grammar for `[“hi”,
>>> “hello”, “hey”].sampling(2)`, “>From array, get a sampling of 2"
>>>
>>
>> "Sampling" is fine.
>>
>>
>> On Nov 23, 2017, 12:54 AM -0600, Xiaodi Wu , wrote:
>>>
>>> On Wed, Nov 22, 2017 at 23:01 Alejandro Alonso <aalonso128 at outlook.com>
>>> wrote:
>>>
>>>> Like I’ve said, python has different syntax grammar. We have to read
>>>> each call site and form a sentence from it. `random.choice([1, 2, 3])` to
>>>> me this reads, “Get a random choice from array”. This makes sense. Slapping
>>>> the word choice as an instance property like `[1, 2, 3].choice` reads,
>>>> “From array, get choice”. What is choice? This doesn’t make sense at all to
>>>> me. To me, the only good solution is `[1, 2, 3].random` which reads, “From
>>>> array, get random”. I actually think most users will be able to understand
>>>> this at first glance rather than choice (or any or some).
>>>>
>>>
>>> Again, my concern here is that you are proposing to name multiple things
>>> "random". If this property should be called "random"--which I'm fine
>>> with--then the static method "random(in:)" should be named something else,
>>> and the static property "random" should be dropped altogether (as I
>>> advocate for reasons we just discussed) or renamed as well. It is simply
>>> too confusing that there are so many different "random" methods or
>>> properties. Meanwhile, isn't your default RNG also going to be called
>>> something like "DefaultRandom"?
>>>
>>> In regards to the sample() function on collections, I have added this as
>>>> I do believe this is something users need. The name I gave it was pick() as
>>>> this reads, “From array, pick 2”.
>>>>
>>>
>>> The name "sample" has been used to good effect in other languages, has a
>>> well understood meaning in statistics, and is consistent with Swift
>>> language guidelines. The operation here is a sampling, and per Swift
>>> guidelines the name must be a noun: therefore, 'sample' is fitting. "Pick"
>>> does not intrinsically suggest randomness, whereas sample does, and your
>>> proposed reading uses it as a verb, whereas Swift guidelines tell us it
>>> must be a noun. I would advocate strongly for using well-established
>>> terminology and sticking with "sample."
>>>
>>>
>>> On Nov 17, 2017, 8:32 PM -0600, Xiaodi Wu via swift-evolution <
>>>> swift-evolution at swift.org>, wrote:
>>>>
>>>> On Fri, Nov 17, 2017 at 7:11 PM, Brent Royal-Gordon <brent@
>>>> architechies.com> wrote:
>>>>
>>>>> On Nov 17, 2017, at 3:09 PM, Xiaodi Wu via swift-evolution <
>>>>> swift-evolution at swift.org> wrote:
>>>>>
>>>>> But actually, Int.random followed by % is the much bigger issue and a
>>>>> very good cautionary tale for why T.random is not a good idea. Swift should
>>>>> help users do the correct thing, and getting a random value across the full
>>>>> domain and computing an integer modulus is never the correct thing to do
>>>>> because of modulo bias, yet it's a very common error to make. We are much
>>>>> better off eliminating this API and encouraging use of the correct API,
>>>>> thereby reducing the likelihood of users making this category of error.
>>>>>
>>>>>
>>>>> Amen.
>>>>>
>>>>> If (and I agree with this) the range-based notation is less intuitive
>>>>> (0..<10.random is certainly less discoverable than Int.random), then we
>>>>> ought to offer an API in the form of `Int.random(in:)` but not
>>>>> `Int.random`. This does not preclude a `Collection.random` API as Alejandro
>>>>> proposes, of course, and that has independent value as Gwendal says.
>>>>>
>>>>>
>>>>> If we're not happy with the range syntax, maybe we should put
>>>>> `random(in:)`-style methods on the RNG protocol as extension methods
>>>>> instead. Then there's a nice, uniform style:
>>>>>
>>>>> let diceRoll = rng.random(in: 1...6)
>>>>> let card = rng.random(in: deck)
>>>>> let isHeads = rng.random(in: [true, false])
>>>>> let probability = rng.random(in: 0.0...1.0) // Special FloatingPoint
>>>>> overload
>>>>>
>>>>> The only issue is that this makes the default RNG's name really
>>>>> important. Something like:
>>>>>
>>>>> DefaultRandom.shared.random(in: 1...6)
>>>>>
>>>>> Will be a bit of a pain for users.
>>>>>
>>>>
>>>> I did in fact implement this style of RNG in NumericAnnex, but I'm not
>>>> satisfied with the design myself. Not only is it a bit of an ergonomic
>>>> thorn, there's also another drawback that actually has weighty implications:
>>>>
>>>> Users aren't conditioned to reuse RNG instances. Perhaps, it is because
>>>> it can "feel" wrong that multiple random instances should come from the
>>>> *same* RNG. Instead, it "feels" more right to initialize a new RNG for
>>>> every random number. After all, if one RNG is random, two must be randomer!
>>>> This error is seen with some frequency in other languages that adopt this
>>>> design, and they sometimes resort to educating users through documentation
>>>> that isn't consistently heeded.
>>>>
>>>> Of course, you and I both know that this is not ideal for performance.
>>>> Moreover, for a number of PRNG algorithms, the first few hundred or
>>>> thousand iterations can be more predictable than later iterations. (Some
>>>> algorithms discard the first n iterations, but whether that's adequate
>>>> depends on the quality of the seed, IIUC.) Both of these issues don't apply
>>>> specifically to a default RNG type that cannot be initialized and always
>>>> uses entropy from the global pool, but that's not enough to vindicate the
>>>> design, IMO. By emphasizing *which* RNG instance is being used for random
>>>> number generation, the design encourages non-reuse of non-default RNGs,
>>>> which is precisely where this common error matters for performance (and
>>>> maybe security).
>>>>
>>>> Maybe we call the default RNG instance `random`, and then give the
>>>>> `random(in:)` methods another name, like `choose(in:)`?
>>>>>
>>>>> let diceRoll = random.choose(in: 1...6)
>>>>> let card = random.choose(in: deck)
>>>>> let isHeads = random.choose(in: [true, false])
>>>>> let probability = random.choose(in: 0.0...1.0)
>>>>> let diceRoll = rng.choose(in: 1...6)
>>>>> let card = rng.choose(in: deck)
>>>>> let isHeads = rng.choose(in: [true, false])
>>>>> let probability = rng.choose(in: 0.0...1.0)
>>>>>
>>>>> This would allow us to keep the default RNG's type private and expose
>>>>> it only as an existential—which means more code will treat RNGs as black
>>>>> boxes, and people will extend the RNG protocol instead of the default RNG
>>>>> struct—while also putting our default random number generator under the
>>>>> name `random`, which is probably where people will look for such a thing.
>>>>>
>>>>
>>>> I've said this already in my feedback, but it can get lost in the long
>>>> chain of replies, so I'll repeat myself here because it's relevant to the
>>>> discussion. I think one of the major difficulties of discussing the
>>>> proposed design is that Alejandro has chosen to use a property called
>>>> "random" to name multiple distinct functions which have distinct names in
>>>> other languages. In fact, almost every method or function is being named
>>>> "random." We are tripping over ourselves and muddling our thinking (or at
>>>> least, I find myself doing so) because different things have the exact same
>>>> name, and if I'm having this trouble after deep study of the design, I
>>>> think it's a good sign that this is going to be greatly confusing to users
>>>> generally.
>>>>
>>>> First, there's Alejandro's _static random_, which he proposes to return
>>>> an instance of type T given a type T. In Python, this is named `randint(a,
>>>> b)` for integers, and `random` (between 0 and 1) or `uniform(a, b)` for
>>>> floating-type types. The distinct names reflect the fact that `randint` and
>>>> `uniform` are mathematically quite different (one samples a *discrete*
>>>> uniform distribution and the other a *continuous* uniform distribution),
>>>> and I'm not aware of non-numeric types offering a similar API in Python.
>>>> These distinct names accurately reflect critiques from others on this list
>>>> that the proposed protocol `Randomizable` lumps together types that don't
>>>> share any common semantics for their _static random_ method, and that the
>>>> protocol is of questionable utility because types in general do not share
>>>> sufficient semantics such that one can do interesting work in generic code
>>>> with such a protocol.
>>>>
>>>> Then there's Alejandro's _instance random_, which he proposes to return
>>>> an element of type T given a instance of a collection of type T. In Python,
>>>> this is named "choice(seq)" (for one element, or else throws an error) and
>>>> "sample(seq, k)" (for up to k elements). As I noted, Alejandro was right to
>>>> draw an analogy between _instance random_ and other instance properties of
>>>> a Collection such as `first` and `last`. In fact, the behavior of Python's
>>>> "choice" (if modified to return an Optional) and "sample", as a pair, would
>>>> fit in very well next to Swift's existing pairs of `first` and `prefix(k)`
>>>> and `last` and `suffix(k)`. We could trivially Swiftify the names here; for
>>>> example:
>>>>
>>>> ```
>>>> [1, 2, 3].first
>>>> [1, 2, 3].any // or `choice`, or `some`, or...
>>>> [1, 2, 3].last
>>>>
>>>> [1, 2, 3].prefix(2)
>>>> [1, 2, 3].sample(2)
>>>> [1, 2, 3].suffix(2)
>>>> ```
>>>>
>>>> I'm going to advocate again for _not_ naming all of these distinct
>>>> things "random". Even in conducting this discussion, it's so hard to keep
>>>> track of what particular function a person is giving feedback about.
>>>>
>>>>
>>>> _______________________________________________
>>>> swift-evolution mailing list
>>>> swift-evolution at swift.org
>>>> https://lists.swift.org/mailman/listinfo/swift-evolution
>>>>
>>>>
>>> On Nov 17, 2017, 8:32 PM -0600, Xiaodi Wu via swift-evolution <
>>> swift-evolution at swift.org>, wrote:
>>>
>>> On Fri, Nov 17, 2017 at 7:11 PM, Brent Royal-Gordon <brent@
>>> architechies.com> wrote:
>>>
>>>> On Nov 17, 2017, at 3:09 PM, Xiaodi Wu via swift-evolution <
>>>> swift-evolution at swift.org> wrote:
>>>>
>>>> But actually, Int.random followed by % is the much bigger issue and a
>>>> very good cautionary tale for why T.random is not a good idea. Swift should
>>>> help users do the correct thing, and getting a random value across the full
>>>> domain and computing an integer modulus is never the correct thing to do
>>>> because of modulo bias, yet it's a very common error to make. We are much
>>>> better off eliminating this API and encouraging use of the correct API,
>>>> thereby reducing the likelihood of users making this category of error.
>>>>
>>>>
>>>> Amen.
>>>>
>>>> If (and I agree with this) the range-based notation is less intuitive
>>>> (0..<10.random is certainly less discoverable than Int.random), then we
>>>> ought to offer an API in the form of `Int.random(in:)` but not
>>>> `Int.random`. This does not preclude a `Collection.random` API as Alejandro
>>>> proposes, of course, and that has independent value as Gwendal says.
>>>>
>>>>
>>>> If we're not happy with the range syntax, maybe we should put
>>>> `random(in:)`-style methods on the RNG protocol as extension methods
>>>> instead. Then there's a nice, uniform style:
>>>>
>>>> let diceRoll = rng.random(in: 1...6)
>>>> let card = rng.random(in: deck)
>>>> let isHeads = rng.random(in: [true, false])
>>>> let probability = rng.random(in: 0.0...1.0) // Special FloatingPoint
>>>> overload
>>>>
>>>> The only issue is that this makes the default RNG's name really
>>>> important. Something like:
>>>>
>>>> DefaultRandom.shared.random(in: 1...6)
>>>>
>>>> Will be a bit of a pain for users.
>>>>
>>>
>>> I did in fact implement this style of RNG in NumericAnnex, but I'm not
>>> satisfied with the design myself. Not only is it a bit of an ergonomic
>>> thorn, there's also another drawback that actually has weighty implications:
>>>
>>> Users aren't conditioned to reuse RNG instances. Perhaps, it is because
>>> it can "feel" wrong that multiple random instances should come from the
>>> *same* RNG. Instead, it "feels" more right to initialize a new RNG for
>>> every random number. After all, if one RNG is random, two must be randomer!
>>> This error is seen with some frequency in other languages that adopt this
>>> design, and they sometimes resort to educating users through documentation
>>> that isn't consistently heeded.
>>>
>>> Of course, you and I both know that this is not ideal for performance.
>>> Moreover, for a number of PRNG algorithms, the first few hundred or
>>> thousand iterations can be more predictable than later iterations. (Some
>>> algorithms discard the first n iterations, but whether that's adequate
>>> depends on the quality of the seed, IIUC.) Both of these issues don't apply
>>> specifically to a default RNG type that cannot be initialized and always
>>> uses entropy from the global pool, but that's not enough to vindicate the
>>> design, IMO. By emphasizing *which* RNG instance is being used for random
>>> number generation, the design encourages non-reuse of non-default RNGs,
>>> which is precisely where this common error matters for performance (and
>>> maybe security).
>>>
>>> Maybe we call the default RNG instance `random`, and then give the
>>>> `random(in:)` methods another name, like `choose(in:)`?
>>>>
>>>> let diceRoll = random.choose(in: 1...6)
>>>> let card = random.choose(in: deck)
>>>> let isHeads = random.choose(in: [true, false])
>>>> let probability = random.choose(in: 0.0...1.0)
>>>> let diceRoll = rng.choose(in: 1...6)
>>>> let card = rng.choose(in: deck)
>>>> let isHeads = rng.choose(in: [true, false])
>>>> let probability = rng.choose(in: 0.0...1.0)
>>>>
>>>> This would allow us to keep the default RNG's type private and expose
>>>> it only as an existential—which means more code will treat RNGs as black
>>>> boxes, and people will extend the RNG protocol instead of the default RNG
>>>> struct—while also putting our default random number generator under the
>>>> name `random`, which is probably where people will look for such a thing.
>>>>
>>>
>>> I've said this already in my feedback, but it can get lost in the long
>>> chain of replies, so I'll repeat myself here because it's relevant to the
>>> discussion. I think one of the major difficulties of discussing the
>>> proposed design is that Alejandro has chosen to use a property called
>>> "random" to name multiple distinct functions which have distinct names in
>>> other languages. In fact, almost every method or function is being named
>>> "random." We are tripping over ourselves and muddling our thinking (or at
>>> least, I find myself doing so) because different things have the exact same
>>> name, and if I'm having this trouble after deep study of the design, I
>>> think it's a good sign that this is going to be greatly confusing to users
>>> generally.
>>>
>>> First, there's Alejandro's _static random_, which he proposes to return
>>> an instance of type T given a type T. In Python, this is named `randint(a,
>>> b)` for integers, and `random` (between 0 and 1) or `uniform(a, b)` for
>>> floating-type types. The distinct names reflect the fact that `randint` and
>>> `uniform` are mathematically quite different (one samples a *discrete*
>>> uniform distribution and the other a *continuous* uniform distribution),
>>> and I'm not aware of non-numeric types offering a similar API in Python.
>>> These distinct names accurately reflect critiques from others on this list
>>> that the proposed protocol `Randomizable` lumps together types that don't
>>> share any common semantics for their _static random_ method, and that the
>>> protocol is of questionable utility because types in general do not share
>>> sufficient semantics such that one can do interesting work in generic code
>>> with such a protocol.
>>>
>>> Then there's Alejandro's _instance random_, which he proposes to return
>>> an element of type T given a instance of a collection of type T. In Python,
>>> this is named "choice(seq)" (for one element, or else throws an error) and
>>> "sample(seq, k)" (for up to k elements). As I noted, Alejandro was right to
>>> draw an analogy between _instance random_ and other instance properties of
>>> a Collection such as `first` and `last`. In fact, the behavior of Python's
>>> "choice" (if modified to return an Optional) and "sample", as a pair, would
>>> fit in very well next to Swift's existing pairs of `first` and `prefix(k)`
>>> and `last` and `suffix(k)`. We could trivially Swiftify the names here; for
>>> example:
>>>
>>> ```
>>> [1, 2, 3].first
>>> [1, 2, 3].any // or `choice`, or `some`, or...
>>> [1, 2, 3].last
>>>
>>> [1, 2, 3].prefix(2)
>>> [1, 2, 3].sample(2)
>>> [1, 2, 3].suffix(2)
>>> ```
>>>
>>> I'm going to advocate again for _not_ naming all of these distinct
>>> things "random". Even in conducting this discussion, it's so hard to keep
>>> track of what particular function a person is giving feedback about.
>>>
>>>
>>>
>>> _______________________________________________
>>> swift-evolution mailing list
>>> swift-evolution at swift.org
>>> https://lists.swift.org/mailman/listinfo/swift-evolution
>>>
>>>
>>
>> _______________________________________________
>> swift-evolution mailing list
>> swift-evolution at swift.org
>> https://lists.swift.org/mailman/listinfo/swift-evolution
>>
>>
>> _______________________________________________
>> swift-evolution mailing list
>> swift-evolution at swift.org
>> https://lists.swift.org/mailman/listinfo/swift-evolution
>>
>>
>>
>> _______________________________________________
>> 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/20171130/e1f56cef/attachment.html>


More information about the swift-evolution mailing list