> 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 >> <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