├── .github └── workflows │ ├── build.yml │ └── deploy.yml ├── .gitignore ├── .npmrc ├── LICENSE ├── README.md ├── package.json └── spec.emu /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build spec 2 | 3 | on: [pull_request, push] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | 9 | steps: 10 | - uses: actions/checkout@v3 11 | - uses: ljharb/actions/node/install@main 12 | name: 'nvm install lts/* && npm install' 13 | with: 14 | node-version: lts/* 15 | - run: npm run build 16 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: Deploy gh-pages 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | deploy: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/checkout@v3 14 | - uses: ljharb/actions/node/install@main 15 | name: 'nvm install lts/* && npm install' 16 | with: 17 | node-version: lts/* 18 | - run: npm run build 19 | - uses: JamesIves/github-pages-deploy-action@v4.3.3 20 | with: 21 | branch: gh-pages 22 | folder: build 23 | clean: true 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # nyc test coverage 18 | .nyc_output 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directories 30 | node_modules 31 | jspm_packages 32 | 33 | # Optional npm cache directory 34 | .npm 35 | 36 | # Optional REPL history 37 | .node_repl_history 38 | 39 | # Only apps should have lockfiles 40 | yarn.lock 41 | package-lock.json 42 | npm-shrinkwrap.json 43 | pnpm-lock.yaml 44 | 45 | # Build directory 46 | build 47 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 ECMA TC39 and contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Random Namespace and Functions 2 | 3 | * Stage 0 4 | * Authors: WhosyVox and Tab Atkins-Bittner 5 | * Champions: Tab Atkins-Bittner 6 | * Spec Text: currently this README 7 | 8 | ----- 9 | 10 | Historically, JS has offered a very simple API for randomness: a `Math.random()` function that returns a random value from a uniform distribution in the range [0,1), with no further details. This is a perfectly adequate primitive, but most actual applications end up having to wrap this into other functions to do something useful, such as obtaining random *integers* in a particular range, or sampling a *normal* distribution. While it's not hard to write many of these utility functions, doing so *correctly* can be non-trivial: off-by-one errors are easy to hit even in simple "simulate a die roll" cases. 11 | 12 | This proposal aims to introduces a number of convenient functions for dealing with random values, to make common random-related code both easier to use and less suseptable to errors. 13 | 14 | Goals: 15 | * To introduce at least the lowest common set of methods for properly generating random numbers 16 | * To build a consistent API between the pre-seeded and seeded PRNG proposal 17 | * To use readily understood method names, in line with what has been done in other areas of the language (e.g. `Array.toSorted`, `Array.at`, `Object.fromEntries`) 18 | 19 | The proposal is split across four somewhat-independent parts: 20 | 21 | 1. Creating a new `Random` namespace object to host the methods. 22 | 2. Defining several new families of random functions. 23 | 3. Specifying the PRNG algorithm. 24 | 4. Applying all the functions to `SeededPRNG` as well. 25 | 26 | # Part 1: The `Random` Namespace 27 | 28 | The current `Math.random()` method is on the `Math` namespace object, 29 | presumably just because it had to live *somewhere* 30 | and since it returns a number `Math` seemed vaguely appropriate. 31 | 32 | We propose that a new `Random` namespace object be created, 33 | to host all of the proposed methods of this proposal. 34 | A new `Random` namespace obviates the need to put "random" into the names of all the new methods. 35 | Several of the proposed new methods also do not directly produce numbers, 36 | so putting them on `Math` seems inappropriate. 37 | 38 | The existing `Math.random()` method will be left as-is (back compat requires that), 39 | but will also be duplicated into `Random`, as `Random.random()`, for consistency. 40 | 41 | We do not propose doing anything with the existing `Crypto` namespace methods that deal with randomness. 42 | They are specifically generating *cryptographically strong* randomness, 43 | which is not a concern of these methods in general, 44 | and their API shapes are geared towards cryptographic use-cases, 45 | which are not necessarily reasonable for JS API design in general. 46 | 47 | # Part 2: The New Random Functions 48 | 49 | There is a very large family of functions we could *potentially* include. 50 | The list here is, at this point, intentionally a bit large to show off the possibilities; 51 | it is expected that the committee's feedback will reduce them to a smaller set. 52 | 53 | ## Random Numbers 54 | 55 | * `random()`: Identical to the existing `Math.random()`. Takes no arguments, returns a random Number in the range `[0, 1)` (that is, containing 0 but not 1), with a uniform distribution. 56 | * `number(lo, hi, step?)`: Returns a random `Number` in the range `[lo, hi)` (that is, containing `lo` but not `hi`), with a uniform distribution. Similar to the `Iterator.range()` proposal, third argument can be a step value, or an options bag specifying the step value and whether or not to include the endpoint. 57 | * If `step` is defined, instead generates a random integer `R` and returns `lo + step*R`, with `R` ranging from 0 to a maximum that ensures the return value is less than or equal to `hi`. (Details below.) 58 | * All arguments must be `Number`s, or else `TypeError`. 59 | * `int(lo, hi, step?)`: Returns a random integral `Number` in the range `[lo, hi]` (that is, containing both `lo` and `hi`), with a uniform distribution. `step` works the same as in `number()`. 60 | * All arguments must be integers, or else `TypeError`. 61 | * `bigint(lo, hi, step?)`: Identical to `int()`, but returns a `BigInt` instead. All arguments must be `BigInt`s, or else `TypeError`. 62 | 63 | > [!NOTE] 64 | > Do we want to enforce an ordering for `lo` and `hi`, or allow them to be out of order? I lean towards allowing them in either order, especially since for `number()` the range is asymmetric; whether you want `[-2, -5)` or `(-2, -5]` can be application-specific. Also, which value is "low" when negatives are used is ambiguous anyway; `Random.number(-2, -5)` and `Random.number(-5, -2)` both potentially look correct. 65 | 66 | > [!NOTE] 67 | > The `step` behavior for these methods is taken directly from [CSS's `random()` function](https://drafts.csswg.org/css-values-5/#random). 68 | > It's also [being proposed for `Iterator.range()`](https://github.com/tc39/proposal-iterator.range/issues/64#issuecomment-2881243363). 69 | 70 | > [!NOTE] 71 | > It's probably generally a good thing to match [`Iterator.range()`](https://github.com/tc39/proposal-iterator.range/) in the signatures, which would mean including `inclusive` as an options-bag argument, and defaulting `int()` and `bigint()` (and `number()` using a `step`) to exclude their `hi` value. That would mean, to simulate a d6, you'd need to write either `Random.int(1, 7)` or `Random.int(1, 6, {inclusive:true})`. I'm unsure if the consistency is worth it, tho... 72 | 73 | ## Collection Methods 74 | 75 | * `shuffle(arr)`: Shuffles an `Array` in-place. Argument must be an `Array` or Array-like. 76 | * `toShuffled(coll)`: Returns a fresh `Array` containing the shuffled values from `coll`, which can be any iterable. 77 | * `take(coll, n, {replace, counts, weights})`: Returns an `Array` containing `n` randomly-selected entries from `coll` (which must be an iterable). Optional arguments are: 78 | * `replace`, a boolean: if `false` (the default), takes without replacement; every value in the returned Array will be from a unique index in the original collection. (If `n` is larger than the length of `coll`, throw a `RangeError`?) If `true`, takes with replacement; values in the returned Array can be repeats (and you can take any amount of them without error). 79 | * `counts`, an iterable of non-negative integers: provides the "multiplicity" of the entries in the matching index from `coll`. `Random.take(["a", "b"], 2, {counts:[2, 3]})` is identical to `Random.take(["a", "a", "b", "b", "b"], 2)`. (In particular, this example could return "a" or "b" multiple times, even tho it's not using replacement, just like the desugared example can.) If omitted, all counts are `1`. If the iterable is too short, missing entries are treated as `0`; if too long, excess entries are ignored. 80 | * `weights`, an iterable of non-negative numbers: provides the "weight" for the entries in the matching index from `coll`, allowing some entries to be more likely to be selected than others. If omitted, all weights are `1`. If the iterable is too short, missing entries are treated as `0`; if too long, excess entries are ignored. 81 | * `counts` and `weights` can be used together; the specified weight for an entry is treated as applying to each of the multiple implied entries (not divided between them). That is, `Random.take(["a", "b"], 2, {counts: [2, 3], weights:[1, 2]})` is equivalent to `Random.take(["a", "a", "b", "b", "b"], 2, {weights: [1, 1, 2, 2, 2]})`. 82 | * `sample(coll, {counts, weights})`: Returns one item from the collection. Options are identical to `take()`. `Random.sample(coll, options)` is identical to `Random.take(coll, 1, options)[0]`, just without constructing the intermediate `Array`. 83 | 84 | ## Distribution Methods 85 | 86 | [Python includes a decent selection of distributions.](https://docs.python.org/3/library/random.html#discrete-distributions) 87 | We should probably *at least* include the normal/gaussian distribution, given its high degree of usefulness. Should we include more? All of Python's distributions? Other distributions? 88 | 89 | * `normal(mean=0, stdev=1)`: the gaussian/normal distribution 90 | * `lognormal(mean=0, stdev=1)`: the [lognormal distribution](https://en.wikipedia.org/wiki/Log-normal_distribution) - a distribution whose *log* has a `normal(mean,stdev)` distribution 91 | * `vonmisse(mean=0, kappa=0)`: the [von Misse distribution](https://en.wikipedia.org/wiki/Von_Mises_distribution), basically a gaussian over a circle 92 | * `triangular(lo=0, hi=1, mode=(lo+hi)/2)`: a [triangular distribution](https://en.wikipedia.org/wiki/Triangular_distribution) with a high point of `mode`. 93 | * `exponential(lambda=1)`: an [exponential distribution](https://en.wikipedia.org/wiki/Exponential_distribution) (from 0 to infinity). (The mean is `1/lambda`.) 94 | * `binomial(n, p=0.5)`: the [binomial distribution](https://en.wikipedia.org/wiki/Binomial_distribution) - how many successes in N trials with P chance of success? (sampling with replacement) 95 | * `geometric(p=.5)`: the [geometric distribution](https://en.wikipedia.org/wiki/Geometric_distribution) - how many failures before the first success, with P chance of success? (sampling with replacement) 96 | * `hypergeometric(n, N, K)`: the [hyper-geometric distribution](https://en.wikipedia.org/wiki/Hypergeometric_distribution) - how many successes in n trials if exactly K of the N possible outcomes are a success? (sampling *without* replacement) 97 | * `beta(alpha, beta)`: the [beta distribution](https://en.wikipedia.org/wiki/Beta_distribution) 98 | * `gamma(alpha, beta)`: the [gamma distribution](https://en.wikipedia.org/wiki/Gamma_distribution) 99 | * `pareto(alpha)`: the [Pareto distribution](https://en.wikipedia.org/wiki/Pareto_distribution) 100 | * `weibull(alpha, beta)`: the [Weibull distribution](https://en.wikipedia.org/wiki/Weibull_distribution) 101 | 102 | ## Byte Methods 103 | 104 | * `bytes(n)`: Returns a `Uint8Array` of the specified length, filled with random bytes. 105 | * `fillBytes(buffer)`: Fills the passed typed array/view/etc with random bytes. (Pass views to fill only a chunk of an array.) 106 | 107 | ## Other Value Methods 108 | 109 | * `boolean(p=0.5)`: Returns a bool, with propability `p` of returning `true`. (Exactly equivalent to `Random.random() < p`, so possibly not worth it?) 110 | 111 | # Part 3: Specifying The PRNG 112 | 113 | `Math.random()` was historically unspecified, and because some benchmarks unintentionally depended on it, has been driven toward a fast but terrible PRNG algorithm in implementations. 114 | 115 | We propose that the `Random` methods use a *specified* PRNG algorithm, specifically the same one used by the [Seeded Random proposal](https://github.com/tc39/proposal-seeded-random/) - ChaCha12. This algorithm produces high-quality pseudo-randomness (but intentionally not cryptographic quality), and has good performance characteristics relative to PRNGs of similar quality. Plus, it would mean a shared implementation between the two very similar proposals, which is likely appealing to implementations, since all of the `Random` methods are intended to appear on `SeededPRNG` as well. 116 | 117 | We will probably leave the choice of initial PRNG state to still be up to the UA. 118 | 119 | # Part 4: Interaction with `SeededPRNG` 120 | 121 | It is intended that this proposal and the [Seeded Random proposal](https://github.com/tc39/proposal-seeded-random/) expose the same APIs; every method on `Random` should exist on `SeededPRNG` objects as well. Either proposal can advance ahead of the other, however. This proposal is intentionally not touching seeded randomness, instead focusing on functions that are agnostic as to their random source. 122 | 123 | # Prior Art 124 | 125 | * [Python's `random` module](https://docs.python.org/3/library/random.html) 126 | * random float, with any min/max bounds (and any step) 127 | * random ints, with any min/max bounds (and any step) 128 | * random bytes 129 | * random selection (or N selections with replacement) from a list, with optional weights 130 | * random sampling (or N samples, *without* replacement) from a list, with optional counts 131 | * randomly shuffle an array 132 | * sample various random distributions: binomal, triangular, beta, exponential, gamma, normal, log-normal, von Mises, Pareto, Weibull 133 | * [.Net's `Random` class](https://learn.microsoft.com/en-us/dotnet/api/system.random?view=net-8.0) 134 | * random ints, with any min/max bounds 135 | * random floats, with any min/max bounds 136 | * random bytes 137 | * randomly shuffle an array 138 | * [Haskell's `RandomGen` interface](https://hackage.haskell.org/package/random-1.2.1.2/docs/System-Random.html) 139 | * random u8/u16/u32/u64, either across the full range or between 0 and max 140 | * generate two RNGs from the starting RNG (seeded-random use-case) 141 | * random from any type with a range (like all the numeric types, enums, etc), or tuples of such 142 | * random bytes 143 | * random floats between 0 and 1 144 | * [Ruby's `Random` class](https://ruby-doc.org/core-2.4.0/Random.html) 145 | * random floats between 0 and max 146 | * random ints between 0 and max 147 | * random bytes 148 | * [Common Lisp's `(random n)` function]([https://www.cs.cmu.edu/Groups/AI/html/cltl/clm/node133.html](http://clhs.lisp.se/Body/f_random.htm)) 149 | * random ints between 0 and max 150 | * random floats between 0 and max 151 | * [Java's `Random` class](https://docs.oracle.com/javase/8/docs/api/java/util/Random.html) 152 | * random doubles, defaulting to [0,1) but can be given any min/max bounds 153 | * random ints (or longs), with any min/max bounds 154 | * random bools 155 | * random bytes 156 | * sample Gaussian distribution 157 | * [JS `genTest` library](https://www.npmjs.com/package/gentest) 158 | * random ints (within certain classes) 159 | * random characters 160 | * random "strings" (relatively short but random lengths, random characters) 161 | * random bools 162 | * random selection (N, with replacement) from a list 163 | * custom random generators 164 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "template-for-proposals", 4 | "description": "A repository template for ECMAScript proposals.", 5 | "scripts": { 6 | "start": "npm run build-loose -- --watch", 7 | "build": "npm run build-loose -- --strict", 8 | "build-loose": "node -e 'fs.mkdirSync(\"build\", { recursive: true })' && ecmarkup --load-biblio @tc39/ecma262-biblio --verbose spec.emu build/index.html --lint-spec" 9 | }, 10 | "homepage": "https://github.com/tc39/template-for-proposals#readme", 11 | "repository": { 12 | "type": "git", 13 | "url": "git+https://github.com/tc39/template-for-proposals.git" 14 | }, 15 | "license": "MIT", 16 | "devDependencies": { 17 | "@tc39/ecma262-biblio": "^2.1.2571", 18 | "ecmarkup": "^17.0.0" 19 | }, 20 | "engines": { 21 | "node": ">= 12" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /spec.emu: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |
 7 | title: Proposal Title Goes Here
 8 | stage: -1
 9 | contributors: Your Name(s) Here
10 | 
11 | 12 | 13 |

This is an emu-clause

14 |

This is an algorithm:

15 | 16 | 1. Let _proposal_ be *undefined*. 17 | 1. If IsAccepted(_proposal_) is *true*, then 18 | 1. Let _stage_ be *0*. 19 | 1. Else, 20 | 1. Let _stage_ be *-1*. 21 | 1. Return ? ToString(_stage_). 22 | 23 |
24 | 25 | 26 |

27 | IsAccepted ( 28 | _proposal_: an ECMAScript language value 29 | ): a Boolean 30 |

31 |
32 |
description
33 |
Tells you if the proposal was accepted
34 |
35 | 36 | 1. If _proposal_ is not a String, or is not accepted, return *false*. 37 | 1. Return *true*. 38 | 39 |
40 | --------------------------------------------------------------------------------