> On 13 Jan 2018, at 02:24, Jonathan Hull <[email protected]> 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 <[email protected]
>> <mailto:[email protected]>> 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 <[email protected]
>>> <mailto:[email protected]>> 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
>>> <[email protected] <mailto:[email protected]>>, 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
>>>>> [email protected] <mailto:[email protected]>
>>>>> https://lists.swift.org/mailman/listinfo/swift-evolution
>>>>> <https://lists.swift.org/mailman/listinfo/swift-evolution>
>>>>
>>>> _______________________________________________
>>>> swift-evolution mailing list
>>>> [email protected] <mailto:[email protected]>
>>>> https://lists.swift.org/mailman/listinfo/swift-evolution
>>>> <https://lists.swift.org/mailman/listinfo/swift-evolution>
>>
>
_______________________________________________
swift-evolution mailing list
[email protected]
https://lists.swift.org/mailman/listinfo/swift-evolution