I think a full random implementation should be decoupled from Swift's standard 
library and generic random is overkill.

In-language, I think pre-seeded random uniform (0 ..< 1, 
`Double.uniformRandom()`), random int (0 ..< max, `Int.uniform(max)`), and 
random index for indexed collection (`collection.randomIndex()`) is more than 
sufficient, assuming sufficient doc warnings that none of this is suitable for 
encryption or gambling.

-- E

> On Jan 13, 2018, at 6:48 PM, Jonathan Hull via swift-evolution 
> <swift-evolution@swift.org> wrote:
> 
> Basically, my point is that I want to be able to operate generically.
> 
>> On Jan 13, 2018, at 5:20 AM, Letanyan Arumugam <letanya...@gmail.com 
>> <mailto:letanya...@gmail.com>> wrote:
>> 
>> 
>>> On 13 Jan 2018, at 02:24, Jonathan Hull <jh...@gbis.com 
>>> <mailto:jh...@gbis.com>> wrote:
>>> 
>>> I think we have different definitions of consistency.  I am fine with the 
>>> ergonomics of (0…100).random() as a convenience, but it really worries me 
>>> here that everything is special cased.  Special cased things are fine for 
>>> individual projects, but not the standard library.  We should make sure 
>>> that the design is flexible and extensible, and that comes in part from 
>>> having a consistent interface.
>>> 
>> 
>> I think we just want different consistencies. Mine is that I want the same 
>> mental model of having to get a random value from some explicit 
>> ’set’/’space’.
>> 
>>> Also, as I said before, we really shouldn’t be doing these crazy 
>>> contortions to avoid ‘random() % 100’.  Instead we should look for that 
>>> pattern and issue with a warning + fixit to change it to random(in:).  I 
>>> think that will be much more effective in actually changing the behavior in 
>>> the long run.
>>> 
>>> Finally, tying everything to Range is extremely limiting.  I understand if 
>>> we don’t want to add other types to the standard library, but I should be 
>>> able to build on what we add to do it myself without having to reinvent the 
>>> wheel for each type.  It is important to have a consistent story for these 
>>> things (including multi-dimensional types) so that they can interoperate.
>>> 
>> 
>> As a stated above I don’t think of it as being tied to a range, but rather a 
>> set of possible values. If you want to have multi-dimensional generators, 
>> could you not add an extension on an array to generate a value treating the 
>> array's elements as constraints?
>> 
>> Using CGPoint as an example with Nate’s api design of random.
>> 
>> public enum ConstraintKind<T: Comparable> {
>>      case constant(T)
>>      case range(T, T)
>>      case custom((RandomNumberGenerator) -> T)
>> }
>> 
>> public enum PointConstraint {
>>      case x(ConstraintKind<CGFloat>)
>>      case y(ConstraintKind<CGFloat>)
>> }
>> 
>> extension Array where Element == PointConstraint {
>>      func random(from constraintKind: ConstraintKind<CGFloat>,
>>                      using generator: RandomNumberGenerator = Random.default
>>              ) -> CGFloat {
>>              switch constraintKind {
>>              case let .constant(a): return a
>>              case let .range(min, max): return (min...max).random(using: 
>> generator)
>>              case let .custom(f): return f(generator)
>>              }
>>      }
>>      
>>      public func createRandom(using generator: RandomNumberGenerator = 
>> Random.default) -> CGPoint {
>>              var x: CGFloat? = nil
>>              var y: CGFloat? = nil
>>              
>>              for constraint in self {
>>                      switch constraint {
>>                      case let .x(c): x = random(from: c, using: generator)
>>                      case let .y(c): y = random(from: c, using: generator)
>>                      }
>>              }
>>              
>>              return CGPoint(x: x ?? 0.0, y: y ?? 0.0)
>>      }
>> }
>> 
>> let pointSpace: [PointConstraint] = [
>>      .x(.range(2, 32.5)),
>>      .y(.constant(4))
>> ]
>> 
>> pointSpace.createRandom()
>> 
>> 
>> 
>> This uses the idea that constraints create a space of possible CGPoint 
>> values that createRandom 'gets' from.
>> 
>> 
>> You could make array conform to some ConstraintRandom protocol when we get 
>> conditional conformance.
>> 
>>> We really should be looking at GamePlayKit more for design inspiration.  
>>> There are several use-cases there that are being blatantly ignored in this 
>>> discussion.  For example, what if I want to randomly generate a game world 
>>> (e.g. The square from The Battle For Polytopia” formerly “SuperTribes”)?  
>>> Or what if I want an effect where it randomly fades in letters from a 
>>> String.  (…).random() will be completely inadequate for these things.
>>> 
>>> Thanks,
>>> Jon
>>> 
>>> 
>>> 
>>>> On Jan 12, 2018, at 5:11 AM, Letanyan Arumugam <letanya...@gmail.com 
>>>> <mailto:letanya...@gmail.com>> wrote:
>>>> 
>>>> Nate’s design follows a consistent idea of getting a random value from 
>>>> some set of values. Adding the static method random() to a type 
>>>> essentially creates an implicit set which you yourself said leads to 
>>>> inconsistency (Double/Int). Secondly I don’t see why random(in:) should be 
>>>> added when it is just a different spelling for what is already provided. 
>>>> If my second statement is incorrect and there’s something I’m missing 
>>>> please correct me?
>>>> 
>>>> I think that consistency outweighs the random trapping inconsistency, 
>>>> however I would actually be fine if random returned an optional. Though 
>>>> the way random is used would likely lead to less opportunities for a trap 
>>>> than the other methods you mention. 
>>>> 
>>>> 
>>>> Letanyan
>>>> 
>>>>> On 12 Jan 2018, at 04:39, Alejandro Alonso <aalonso...@outlook.com 
>>>>> <mailto:aalonso...@outlook.com>> wrote:
>>>>> 
>>>>> If anything, Nate’s design is inconsistent as properties like `.first` 
>>>>> and `.last` return an optional, and methods like `.min()` and `.max()` 
>>>>> return an optional as well. Having `.random()` on ranges be an exception 
>>>>> and return non optionals are inconsistent with other collection 
>>>>> facilities, and with other collections that aren’t ranges that return 
>>>>> optionals on `.random()`.
>>>>> 
>>>>> - Alejandro
>>>>> 
>>>>> On Jan 11, 2018, 12:06 PM -0600, Letanyan Arumugam via swift-evolution 
>>>>> <swift-evolution@swift.org <mailto:swift-evolution@swift.org>>, wrote:
>>>>>> This is really cool and seems very powerful. However I don’t think we 
>>>>>> should sacrifice consistency for extendability. Especially when the 
>>>>>> extendability would not be what most people need. 
>>>>>> 
>>>>>> What I am basically trying to say is that. I think the proposals current 
>>>>>> design direction fits better in a Random library rather than the 
>>>>>> Standard Library. And Nate’s design more directly addresses the 
>>>>>> motivating points of the proposal.
>>>>>> 
>>>>>> Letanyan
>>>>>> 
>>>>>>> 
>>>>>>> Sure. Small disclaimer that this was originally written back in the 
>>>>>>> Swift 1~2 days, so it is overdue for a simplifying rewrite.
>>>>>>> 
>>>>>>> Also, I should point out that the term “Source” has a special meaning 
>>>>>>> in my code.  It basically means that something will provide an 
>>>>>>> ~infinite collection of values of a type T.   I have what I call a 
>>>>>>> “ConstantSource” which just wraps a T and gives it back when asked.  
>>>>>>> But then I have a bunch of other “sources" which let you create 
>>>>>>> repeating patterns and do deferred calculations and things like that.  
>>>>>>> Finally I have a “RandomSource” which is part of what started this 
>>>>>>> discussion.  You set up a RandomSource with a set of constraints, and 
>>>>>>> then it gives you random values of T that adhere to those constraints 
>>>>>>> (e.g. colors with a range of hues but the same saturation) whenever you 
>>>>>>> ask for them.
>>>>>>> 
>>>>>>> This is really useful for doing things like graphic effects because, 
>>>>>>> for example, I can ask for a source of colors and a source of line 
>>>>>>> widths and then get out a large variety of interesting patterns from 
>>>>>>> the same algorithm.  I can make simple stripes with ConstantSources, or 
>>>>>>> I can make repeating patterns of lines with repeating sources, or I can 
>>>>>>> have random colors which look good together by using a RandomSource.  I 
>>>>>>> can take a BezierPath and make it look hand-drawn by breaking it into a 
>>>>>>> bunch of lines and then offset the points a small amount using a 
>>>>>>> RandomSource of CGVectors.
>>>>>>> 
>>>>>>> Not sure how useful this concept of randomness (and pattern) is to 
>>>>>>> others, but I find it immensely useful!  Not sure of the best way to 
>>>>>>> implement it.  The way I do it is a type erased protocol with private 
>>>>>>> conforming structs and then public initializers on the type-erasing 
>>>>>>> box.  The end result is that I can just say:
>>>>>>> 
>>>>>>> let myConst = Source(1) //ConstantSource with 1 as a value
>>>>>>> let myPattern = Source([1, 2]) //OrderedSource which repeats 1, then 2 
>>>>>>> over and over forever
>>>>>>> let myMeta = Source([myConst, myPattern]) //Will alternate between 
>>>>>>> sub-sources in order. Can be nested.
>>>>>>> //…and so on.
>>>>>>> 
>>>>>>> It is quite extensible and can make very complex/interesting patterns 
>>>>>>> very easily.  What I like about it is that (well controlled) random 
>>>>>>> values and patterns or constant values can be interchanged very easily.
>>>>>>> 
>>>>>>> The RandomSource has a RandomSourceCreatable Protocol that lets it take 
>>>>>>> random bits and turn them into objects/structs of T adhering to the 
>>>>>>> given constraints.  This is way more complex under the hood than it 
>>>>>>> needs to be, but it works well in practice, and I haven’t gotten around 
>>>>>>> to cleaning it up yet:
>>>>>>> 
>>>>>>> public protocol RandomSourceCreatable {
>>>>>>>     associatedtype ConstraintType = Self
>>>>>>>     
>>>>>>>     
>>>>>>> ///This should be implimented by simple types without internal 
>>>>>>> components
>>>>>>>     
>>>>>>> static func createRandom(rnd value:RandomSourceValue, 
>>>>>>> constraint:RandomSourceConstraint<ConstraintType>)->Self
>>>>>>>     
>>>>>>> ///This should be implimented by complex types with multiple axis of 
>>>>>>> constraints
>>>>>>>     
>>>>>>> static func createRandom(rnd value:RandomSourceValue, 
>>>>>>> constraints:[String:RandomSourceConstraint<ConstraintType>])->Self
>>>>>>>     
>>>>>>>     
>>>>>>> ///Returns the proper dimension for the type given the constraints
>>>>>>>     
>>>>>>> static func dimension(given 
>>>>>>> contraints:[String:RandomSourceConstraint<ConstraintType>])->RandomSourceDimension
>>>>>>>     
>>>>>>>     
>>>>>>> ///Validates the given contraints to make sure they can create valid 
>>>>>>> objects. Only needs to be overridden for extremely complex types
>>>>>>>     static func validateConstraints(_ 
>>>>>>> constraints:[String:RandomSourceConstraint<ConstraintType>])->Bool
>>>>>>>     
>>>>>>>     
>>>>>>> ///Convienience method which provides whitelist of keys for implicit 
>>>>>>> validation of constraints
>>>>>>>     static var allowedConstraintKeys:Set<String> {get}
>>>>>>>    }
>>>>>>> 
>>>>>>> Most of these things also have default implementations so you only 
>>>>>>> really have to deal with them for complex cases like colors or points.  
>>>>>>> The constraints are given using a dictionary with string keys and a 
>>>>>>> RandomSourceConstraint value, which is defined like this:
>>>>>>> 
>>>>>>> public enum RandomSourceConstraint<T> {
>>>>>>>     case none
>>>>>>>     case constant(T)
>>>>>>>     case min(T)
>>>>>>>     case max(T)
>>>>>>>     case range (T,T)
>>>>>>>     case custom ( (RandomSourceValue)->T )
>>>>>>> //A bunch of boring convenience code here that transforms values so I 
>>>>>>> don’t always have to switch on the enum in other code that deals with 
>>>>>>> this. I just ask for the bounds or constrained T (Note: T here refers 
>>>>>>> to the type for a single axis as opposed to the generated type. e.g. 
>>>>>>> CGFloat for a point) 
>>>>>>>     }
>>>>>>> 
>>>>>>> I have found that this handles pretty much all of the constraints I 
>>>>>>> need, and the custom constraint is useful for anything exotic (e.g. 
>>>>>>> sig-figs).  The RandomSource itself has convenience inits when T is 
>>>>>>> Comparable that let you specify a range instead of having to create the 
>>>>>>> constraints yourself.
>>>>>>> 
>>>>>>> I then have conformed many standard types to RandomSourceCreatable so 
>>>>>>> that I can create Sources out of them.  Here is CGPoint for reference:
>>>>>>> 
>>>>>>> extension CGPoint:RandomSourceCreatable {
>>>>>>>     
>>>>>>>     
>>>>>>> public static func dimension(given 
>>>>>>> contraints:[String:RandomSourceConstraint<CGFloat>])->RandomSourceDimension
>>>>>>>  {
>>>>>>>         
>>>>>>> return RandomSourceDimension.manyWord(2)
>>>>>>>     }
>>>>>>>     
>>>>>>>     public typealias ConstraintType = CGFloat
>>>>>>>     public static var allowedConstraintKeys:Set<String>{
>>>>>>>         return ["x","y"]
>>>>>>>     }
>>>>>>>     
>>>>>>>     
>>>>>>> public static func createRandom(rnd value:RandomSourceValue, 
>>>>>>> constraints:[String:RandomSourceConstraint<CGFloat>])->CGPoint {
>>>>>>>         let xVal = value.value(at: 0)
>>>>>>>         let yVal = value.value(at: 1)
>>>>>>>         
>>>>>>> //Note: Ints have a better distribution for normal use cases of points
>>>>>>>         let x = CGFloat(Int.createRandom(rnd: xVal, constraint: 
>>>>>>> constraints["x"]?.asType({Int($0 * 1000)}) ?? .none))/1000
>>>>>>>         let y = CGFloat(Int.createRandom(rnd: yVal, constraint: 
>>>>>>> constraints["y"]?.asType({Int($0 * 1000)}) ?? .none))/1000
>>>>>>>         return CGPoint(x: x, y: y)
>>>>>>>     }
>>>>>>>     }
>>>>>>> 
>>>>>>> Notice that I have a RandomSourceValue type that provides the random 
>>>>>>> bits of the requested dimension. When I get around to updating this, I 
>>>>>>> might do something closer to the proposal, where I would just pass the 
>>>>>>> generator and grab bits as needed.  The main reason I did it the way I 
>>>>>>> did is that it lets me have random access to the source very easily.  
>>>>>>> 
>>>>>>> The ‘asType’ method converts a constraint to work with another type (in 
>>>>>>> this case Ints).
>>>>>>> 
>>>>>>> Colors are a bit more complicated, mainly because I allow a bunch of 
>>>>>>> different constraints, and I also have validation code to make sure the 
>>>>>>> constraints fit together properly. I also ask for different amounts of 
>>>>>>> randomness based on whether it is greyscale or contains alpha. Just to 
>>>>>>> give you a sense, here are the allowed constraint keys for a CGColor:
>>>>>>> public static var allowedConstraintKeys:Set<String>{
>>>>>>>         return ["alpha","gray","red","green","blue", "hue", 
>>>>>>> "saturation", "brightness"]
>>>>>>>     }
>>>>>>> 
>>>>>>> and here is the creation method when the keys are for RGBA (I have 
>>>>>>> similar sections for HSBA and greyscale):
>>>>>>> 
>>>>>>> let rVal = value.value(at: 0)
>>>>>>>     let gVal = value.value(at: 1)
>>>>>>>     let bVal = value.value(at: 2)
>>>>>>>     let aVal = value.value(at: 3)
>>>>>>>     let r = CGFloat.createRandom(rnd: rVal, constraint: 
>>>>>>> constraints["red"] ?? .range(0,1))
>>>>>>>     let g = CGFloat.createRandom(rnd: gVal, constraint: 
>>>>>>> constraints["green"] ?? .range(0,1))
>>>>>>>     let b = CGFloat.createRandom(rnd: bVal, constraint: 
>>>>>>> constraints["blue"] ?? .range(0,1))
>>>>>>>     let a = CGFloat.createRandom(rnd: aVal, constraint: 
>>>>>>> constraints["alpha"] ?? .constant(1.0))
>>>>>>>             
>>>>>>>     return self.init(colorSpace: CGColorSpaceCreateDeviceRGB(), 
>>>>>>> components: [r,g,b,a])!
>>>>>>> 
>>>>>>> 
>>>>>>> The end result is that initializing a source of CGColors looks like 
>>>>>>> this (either parameter can be omitted if desired):
>>>>>>> 
>>>>>>> let colorSource:Source<CGColor> = Source(seed: optionalSeed, 
>>>>>>> constraints:["saturation": .constant(0.4), "brightness": 
>>>>>>> .constant(0.6)])
>>>>>>> 
>>>>>>> Anyway, I hope this was useful/informative.  I know the code is a bit 
>>>>>>> messy, but I still find it enormously useful in practice.  I plan to 
>>>>>>> clean it up when I find time, simplifying the RandomSourceValue stuff 
>>>>>>> and moving from String Keys to a Struct with static functions for the 
>>>>>>> constraints.  The new constraints will probably end up looking like 
>>>>>>> this:
>>>>>>> 
>>>>>>> let colorSource:Source<CGColor> = Source(seed: optionalSeed, 
>>>>>>> constraints:[.saturation(0.4), .brightness(0.4...0.6)])
>>>>>>> 
>>>>>>> Thanks,
>>>>>>> Jon
>>>>>>> 
>>>>>>> 
>>>>>>> _______________________________________________
>>>>>>> swift-evolution mailing list
>>>>>>> swift-evolution@swift.org <mailto:swift-evolution@swift.org>
>>>>>>> https://lists.swift.org/mailman/listinfo/swift-evolution 
>>>>>>> <https://lists.swift.org/mailman/listinfo/swift-evolution>
>>>>>> 
>>>>>> _______________________________________________
>>>>>> swift-evolution mailing list
>>>>>> swift-evolution@swift.org <mailto:swift-evolution@swift.org>
>>>>>> https://lists.swift.org/mailman/listinfo/swift-evolution 
>>>>>> <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

Reply via email to