[Proposal] Random Unification

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@gmail.com> wrote:

On 13 Jan 2018, at 02:24, Jonathan Hull <jhull@gbis.com <mailto:jhull@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@gmail.com <mailto:letanyan.a@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@outlook.com <mailto:aalonso128@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

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

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.

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.

I’m not sure what contortions you’re describing—from what I’ve seen, the proposal author is going to revise the proposal to have these ways of generating individual values:

Mainly avoiding ‘random(using:)’ as a thing we can count on generically because of the fear of it being used with mod. This makes random(using:) on Bool a one-off special case instead of a thing I can call on anything adhering to a protocol.

In extensions to FixedWidthInteger and BinaryFloatingPoint:
static func random(in: Range/ClosedRange<Self>, using: RandomNumberGenerator) -> Self

In an extension to Bool:
static func random(using: RandomNumberGenerator) -> Self

If someone still needs a full-width random value as a building-block for generating random instances of other types, they should use the `next()` method directly on a RandomNumberGenerator. In the example code you sent, you could switch to using a RandomNumberGenerator instead of your RandomSourceValue, or base your RandomSourceValue generation on a RandomNumberGenerator instead of whatever random generator you’re using now.

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.

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.

The goal at this point is to build into the standard library the basis for all kinds of other use cases. Your library is one such example of something that can be built on top of the protocol and methods that are being proposed, as are a variety of other tasks, as I tried to show in the playground.

What’s being proposed now is deliberately short of solving every need—the additions would handle the hard stuff (correct and safe generation of integers and floating-points, along with shuffling collections) and lay the groundwork for other libraries to take things farther (by establishing the RandomNumberGenerator, a default generator, and a pattern for their use).

I think we are mostly in agreement on this. I don’t need the proposal to solve every need. I would just really like to see something that those other things can be built on. I don’t expect other types to conform out of the box, but I want the types that I add conformance to to be able to interoperate with machinery that others build around randomness.

For example, if we instead went with Letanyan’s mental model of having to define a space to select a random element from using a generator, that would be perfectly fine for me, since we can conform Range to that protocol… and then we can operate generically on objects which conform to it.

Speaking of GameplayKit, you can make GKRandomSource conform to RandomNumberGenerator in an extension, making all the GK... sources generators. If you’re already depending on those random sources, you’d still have access to them with the proposed model.

Agreed. I was thinking someone was removing the ‘using:’ variant from the proposal for some reason.

Thanks,
Jon

···

On Jan 12, 2018, at 8:22 PM, Nate Cook <natecook@apple.com> wrote:
On Jan 12, 2018, at 6:24 PM, Jonathan Hull via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Nate

Thanks,
Jon

On Jan 12, 2018, at 5:11 AM, Letanyan Arumugam <letanyan.a@gmail.com <mailto:letanyan.a@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@outlook.com <mailto:aalonso128@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

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
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

Could someone measure how bad the "random(32 bits) mod m" problem actually is? It's prominent when the number of bits is close to m, eg 4 bits and m == 3. Is it bad when bits == 32 and m is less than 2^16? Or bits == 64 and m is less than 2^32?

C. Keith Ray
https://leanpub.com/wepntk <- buy my book?
http://agilesolutionspace.blogspot.com/
twitter: @ckeithray
http://www.thirdfoundationsw.com/keith_ray_resume_2014_long.pdf

···

On Jan 13, 2018, at 6:15 PM, Jonathan Hull via swift-evolution <swift-evolution@swift.org> wrote:

On Jan 12, 2018, at 8:22 PM, Nate Cook <natecook@apple.com> wrote:

On Jan 12, 2018, at 6:24 PM, Jonathan Hull via swift-evolution <swift-evolution@swift.org> 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.

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.

I’m not sure what contortions you’re describing—from what I’ve seen, the proposal author is going to revise the proposal to have these ways of generating individual values:

Mainly avoiding ‘random(using:)’ as a thing we can count on generically because of the fear of it being used with mod. This makes random(using:) on Bool a one-off special case instead of a thing I can call on anything adhering to a protocol.

In extensions to FixedWidthInteger and BinaryFloatingPoint:
static func random(in: Range/ClosedRange<Self>, using: RandomNumberGenerator) -> Self

In an extension to Bool:
static func random(using: RandomNumberGenerator) -> Self

If someone still needs a full-width random value as a building-block for generating random instances of other types, they should use the `next()` method directly on a RandomNumberGenerator. In the example code you sent, you could switch to using a RandomNumberGenerator instead of your RandomSourceValue, or base your RandomSourceValue generation on a RandomNumberGenerator instead of whatever random generator you’re using now.

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.

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.

The goal at this point is to build into the standard library the basis for all kinds of other use cases. Your library is one such example of something that can be built on top of the protocol and methods that are being proposed, as are a variety of other tasks, as I tried to show in the playground.

What’s being proposed now is deliberately short of solving every need—the additions would handle the hard stuff (correct and safe generation of integers and floating-points, along with shuffling collections) and lay the groundwork for other libraries to take things farther (by establishing the RandomNumberGenerator, a default generator, and a pattern for their use).

I think we are mostly in agreement on this. I don’t need the proposal to solve every need. I would just really like to see something that those other things can be built on. I don’t expect other types to conform out of the box, but I want the types that I add conformance to to be able to interoperate with machinery that others build around randomness.

For example, if we instead went with Letanyan’s mental model of having to define a space to select a random element from using a generator, that would be perfectly fine for me, since we can conform Range to that protocol… and then we can operate generically on objects which conform to it.

Speaking of GameplayKit, you can make GKRandomSource conform to RandomNumberGenerator in an extension, making all the GK... sources generators. If you’re already depending on those random sources, you’d still have access to them with the proposed model.

Agreed. I was thinking someone was removing the ‘using:’ variant from the proposal for some reason.

Thanks,
Jon

Nate

Thanks,
Jon

On Jan 12, 2018, at 5:11 AM, Letanyan Arumugam <letanyan.a@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@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: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
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

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 <letanyan.a@gmail.com <mailto:letanyan.a@gmail.com>> wrote:

On 13 Jan 2018, at 02:24, Jonathan Hull <jhull@gbis.com <mailto:jhull@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@gmail.com <mailto:letanyan.a@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@outlook.com <mailto:aalonso128@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

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto: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

For a range of 0..<(2<<b) (size being 2<<b) and mod m, its less pronounced for small m. Worst case would be a mod greater than 2^(b-1), in which case some values are twice as likely as others.

The increased probability I believe is (floor(s/m)+ 1) / floor(s/m) for s % m != 0

For me, its not so much about being statistically correct as leading people down the right path in terms of comprehension and readability. I find it much easier to think of the classic example program of “pick a number between 1 and 100” as

(1…100).random()

vs say

Int(UInt64.random()%100) + 1

which (with all that complexity) still isn’t a uniform distribution!

Thats completely ignoring some of the finer issues you get with floating point numbers as you certain transforms of a [0,1] distribution

-DW

···

On Jan 13, 2018, at 7:41 PM, C. Keith Ray via swift-evolution <swift-evolution@swift.org> wrote:

Could someone measure how bad the "random(32 bits) mod m" problem actually is? It's prominent when the number of bits is close to m, eg 4 bits and m == 3. Is it bad when bits == 32 and m is less than 2^16? Or bits == 64 and m is less than 2^32?

C. Keith Ray
https://leanpub.com/wepntk <- buy my book?
http://agilesolutionspace.blogspot.com/
twitter: @ckeithray
http://www.thirdfoundationsw.com/keith_ray_resume_2014_long.pdf

On Jan 13, 2018, at 6:15 PM, Jonathan Hull via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On Jan 12, 2018, at 8:22 PM, Nate Cook <natecook@apple.com <mailto:natecook@apple.com>> wrote:

On Jan 12, 2018, at 6:24 PM, Jonathan Hull via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> 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.

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.

I’m not sure what contortions you’re describing—from what I’ve seen, the proposal author is going to revise the proposal to have these ways of generating individual values:

Mainly avoiding ‘random(using:)’ as a thing we can count on generically because of the fear of it being used with mod. This makes random(using:) on Bool a one-off special case instead of a thing I can call on anything adhering to a protocol.

In extensions to FixedWidthInteger and BinaryFloatingPoint:
static func random(in: Range/ClosedRange<Self>, using: RandomNumberGenerator) -> Self

In an extension to Bool:
static func random(using: RandomNumberGenerator) -> Self

If someone still needs a full-width random value as a building-block for generating random instances of other types, they should use the `next()` method directly on a RandomNumberGenerator. In the example code you sent, you could switch to using a RandomNumberGenerator instead of your RandomSourceValue, or base your RandomSourceValue generation on a RandomNumberGenerator instead of whatever random generator you’re using now.

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.

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.

The goal at this point is to build into the standard library the basis for all kinds of other use cases. Your library is one such example of something that can be built on top of the protocol and methods that are being proposed, as are a variety of other tasks, as I tried to show in the playground.

What’s being proposed now is deliberately short of solving every need—the additions would handle the hard stuff (correct and safe generation of integers and floating-points, along with shuffling collections) and lay the groundwork for other libraries to take things farther (by establishing the RandomNumberGenerator, a default generator, and a pattern for their use).

I think we are mostly in agreement on this. I don’t need the proposal to solve every need. I would just really like to see something that those other things can be built on. I don’t expect other types to conform out of the box, but I want the types that I add conformance to to be able to interoperate with machinery that others build around randomness.

For example, if we instead went with Letanyan’s mental model of having to define a space to select a random element from using a generator, that would be perfectly fine for me, since we can conform Range to that protocol… and then we can operate generically on objects which conform to it.

Speaking of GameplayKit, you can make GKRandomSource conform to RandomNumberGenerator in an extension, making all the GK... sources generators. If you’re already depending on those random sources, you’d still have access to them with the proposed model.

Agreed. I was thinking someone was removing the ‘using:’ variant from the proposal for some reason.

Thanks,
Jon

Nate

Thanks,
Jon

On Jan 12, 2018, at 5:11 AM, Letanyan Arumugam <letanyan.a@gmail.com <mailto:letanyan.a@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@outlook.com <mailto:aalonso128@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

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
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

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto: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

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.

···

On Sat, Jan 13, 2018 at 10:29 PM, Erica Sadun via swift-evolution < swift-evolution@swift.org> wrote:

-- 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 <letanyan.a@gmail.com> > wrote:

On 13 Jan 2018, at 02:24, Jonathan Hull <jhull@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@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@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<
>])->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