Re: [csswg-drafts] [css-values] random() function (#2826)

**(Note: significant reasoning and discussion here; simple proposal sketch in the *next* post if you want to just skip to that.)**

Yeah, just using script-set variables only solves the "randomly determined at parse time" case. It doesn't help if you want to get a different random value per element, unless you go significantly greater lengths.

Dealing with random value "persistence" is the hard problem to solve here. We need to define precisely what data gets fed into the random generator; or more simply, what data we use to *cache* the random values, so we can retrieve the same value when we reevaluate style.

That gets us to the rub; how are you caching the values when you have a rule like

```css
.foo { 
 color: rgb(rand-int(0, 255) rand-int(0, 255) rand-int(0, 255)); 
}
```

The three rand-int calls all need to give distinct values, but need to be persistent across style recalcs. Let's also assume these are meant to be different values on each element, so you can't just do parse-time resolution and call it a day; the functions have to persist until probably computed-value time, at which point they turn into an integer.

Obviously, the element itself is part of the cache key.

What else? Can't just use property; there are three instances and they need to all be distinct.  Maybe property + occurrence count (first, second, third)? That still means that different rules would reuse cached values, which would probably be unexpected: a hover rule setting it to `rgb(127 rand-int(0, 255) rand-int(0, 255))` would use the same value for Green as the other style did for Red, and the same for Blue that the other did for Green.

Maybe key it off of *rule* as well, but we don't have a good, stable notion of rule identity. Changes to a stylesheet can cause a reparse and create totally new objects in the CSSOM, so using OM object-identity is probably bad. Dont' want to, like, hash the rule contents either, as it would mean that changing other properties would alter the random value.

Ultimately, I think the only reasonable cache key, that works reliably and with a minimum of surprise, is an author-provided custom identifier. As long as you provide the same identifier, the random() function should resolve to the same value.

One more wrinkle, then: we don't want to expose internal details of the random data, to allow implementations flexibility to change and be different from each other. Thus, we don't want to reuse the same random number for different random *functions*; the obvious trivial implementation of rand-int() would just modulo a random 32-bit int or something, and using the same value for 1-3 as 1-5 will expose information about the number being used (its value modulo 3 *and* modulo 5).  So we also need to take the range start and end as cache keys.

So: the random values are generated on demand, and cached against:
* an author-supplied custom ident
* the start/end of the range
* the element, if it's meant to vary per-element

This, in addition to being consistent and understandable, has the additional benefit that you can jam a random() into a custom property, and it'll Just Work© without you having to think about it; each use of the var() will get the same value. None of the other possible solutions do this, afaict.

-------------

Now, output spaces. I think it's valuable to have both random integers *and* random numerics (reals, lengths, angles, etc).  Just using a random real in a place that expects integers will *not* work as expected: rand-real(1, 3) will generate a number that rounds to 1 25% of the time, that rounds to 2 50% of the time, and that rounds to 3 25% of the time, a far cry from the 33% you'd like each integer to be generated.  You need a floor() function to write a rand-int() out of rand-real(), and it's non obvious how to do so; I write `Math.randInt()` in JS all the time, and never trust my impl until I run a statistical test on it.

I don't think we need integer-valued dimensions; that's rarely, if ever, actually  useful (as opposed to rounding to larger integers, like a random multiple of 100px), and you can just use `calc(1px * rand-int(1,10))` to get it if you want. (Or `calc(100px * rand-int(3, 5))` for a more realistic case - 300px, 400px, or 500px.)

The real-valued function should accept any numeric value as its start/end, and just require that their types match. (Specifically, that [adding the two types](https://drafts.css-houdini.org/css-typed-om/#cssnumericvalue-add-two-types) is successful, with a null percent hint.)

----------

Lists of values (like, grab a random color from these possibilities) is a valid use-case, but I don't want to solve it directly via a different random function. I think a reasonable use-case is to have *sets* of values that are valid to use together, but that you want a random choice of. As such, I think we should add an `nth(n, val1, val2, ...)` function, and then we can just use rand-int() on it if we want.

-- 
GitHub Notification of comment by tabatkins
Please view or discuss this issue at https://github.com/w3c/csswg-drafts/issues/2826#issuecomment-455347690 using your GitHub account

Received on Thursday, 17 January 2019 21:59:30 UTC