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

Reply via email to