On Sat, Jan 13, 2018 at 10:29 PM, Erica Sadun via swift-evolution < swift-evolution@swift.org> wrote:
> 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. > Agree almost entirely, with the modification that if we limit ourselves to these APIs it should be possible to implement in such a way that suitability for encryption or gambling can be assured, which would be a nice bonus but not a must-have. > -- 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> > wrote: > > > On 13 Jan 2018, at 02:24, Jonathan Hull <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> > 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> 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>, 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:RandomSour > ceConstraint<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:RandomSourc > eConstraint<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 > 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