[swift-evolution] [Proposal] Random Unification
Letanyan Arumugam
letanyan.a at gmail.com
Thu Jan 11 12:06:03 CST 2018
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 at swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20180111/d2618379/attachment.html>
More information about the swift-evolution
mailing list