On Thu, Nov 30, 2017 at 5:29 PM, Jonathan Hull <jh...@gbis.com> wrote:
> > On Nov 30, 2017, at 2:30 PM, Xiaodi Wu <xiaodi...@gmail.com> wrote: > > On Thu, Nov 30, 2017 at 3:58 PM, Dave DeLong via swift-evolution <swift- > evolut...@swift.org> wrote: > >> >> >> On Nov 30, 2017, at 2:48 PM, Jonathan Hull via swift-evolution < >> swift-evolution@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@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 <aalonso...@outlook.com>のメール: >> >> >> - Alejandro >> >> ---------- Forwarded message ---------- >> *From:* Xiaodi Wu <xiaodi...@gmail.com> >> *Date:* Nov 24, 2017, 3:05 PM -0600 >> *To:* Alejandro Alonso <aalonso...@outlook.com> >> *Cc:* Brent Royal-Gordon <br...@architechies.com>, Steve Canon via >> swift-evolution <swift-evolution@swift.org> >> *Subject:* Re: [swift-evolution] [Proposal] Random Unification >> >> On Fri, Nov 24, 2017 at 2:55 PM, Alejandro Alonso <aalonso...@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 <aalonso...@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@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@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@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@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@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@swift.org >>> https://lists.swift.org/mailman/listinfo/swift-evolution >>> >>> >> >> _______________________________________________ >> swift-evolution mailing list >> swift-evolution@swift.org >> https://lists.swift.org/mailman/listinfo/swift-evolution >> >> >> _______________________________________________ >> swift-evolution mailing list >> swift-evolution@swift.org >> https://lists.swift.org/mailman/listinfo/swift-evolution >> >> >> >> _______________________________________________ >> swift-evolution mailing list >> swift-evolution@swift.org >> https://lists.swift.org/mailman/listinfo/swift-evolution > > >
_______________________________________________ swift-evolution mailing list swift-evolution@swift.org https://lists.swift.org/mailman/listinfo/swift-evolution