[swift-evolution] [Proposal] Random Unification

Xiaodi Wu xiaodi.wu at gmail.com
Sat Jan 13 23:20:42 CST 2018


On Sat, Jan 13, 2018 at 10:29 PM, Erica Sadun via swift-evolution <
swift-evolution at 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 at 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 <letanyan.a at gmail.com>
> wrote:
>
>
> On 13 Jan 2018, at 02:24, Jonathan Hull <jhull at 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 <letanyan.a at 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 <aalonso128 at 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 at 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 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/20180113/72c36b6a/attachment.html>


More information about the swift-evolution mailing list