diff --git a/.github/workflows/gh_pages.yaml b/.github/workflows/gh_pages.yaml
index a4aa4d5..9026bd3 100644
--- a/.github/workflows/gh_pages.yaml
+++ b/.github/workflows/gh_pages.yaml
@@ -18,4 +18,4 @@ jobs:
- uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
- publish_dir: ./docs
+ publish_dir: ./generated-docs
diff --git a/README.md b/README.md
index c91a509..5d9bddd 100644
--- a/README.md
+++ b/README.md
@@ -11,25 +11,36 @@ Contributions & feedback are very welcome! :)
## Examples
```coffee
-app "example_simple"
- packages { pf: "./roc/examples/interactive/cli-platform" }
- imports [ pf.Stdout.{ line }, pf.Task.{ await }, Random ]
- provides [ main ] to pf
+# Print a list of 10 random numbers in the range 25-75 inclusive.
+main =
+ # Initialise "randomness"
+ initialSeed = Random.seed16 42
-main =
-
- state = Random.seed 42 # `state` stores the "randomness", initialized by the user/platform.
- int = Random.int 0 100 # `int` generates values from 0-100 (inclusive) and updates state.
- x = int state # x == { value: 9, state: { value: -60952905, ... } }
- y = x |> Random.next int # y == { value: 61, state: { value: 1561666408, ... } }
-
- _ <- await (line (Num.toStr x.value |> \s -> "x: \(s)")) # This will print `x: 9`.
- _ <- await (line (Num.toStr y.value |> \s -> "y: \(s)")) # This will print `x: 61`.
- line "These values will be the same on every run, because we use a constant seed (42)."
+ # Create a generator for values from 25-75 (inclusive)
+ u16 = Random.u16 25 75
+
+ # Create a list of random numbers
+ result =
+ List.range { start: At 0, end: Before 10 }
+ |> List.walk { seed: initialSeed, numbers: [] } \state, _ ->
+
+ random = u16 state.seed
+ seed = random.state
+ numbers = List.append state.numbers random.value
+
+ { seed, numbers }
+
+ # Format as a string
+ numbersListStr =
+ result.numbers
+ |> List.map Num.toStr
+ |> Str.joinWith ","
+
+ Stdout.line "Random numbers are: \(numbersListStr)"
```
-See the `example_*.roc` files for more examples.
+See the `examples/*.roc` files for more examples.
## Documentation
diff --git a/docs/favicon.svg b/docs/favicon.svg
deleted file mode 100644
index e0cff74..0000000
--- a/docs/favicon.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
diff --git a/docs/index.html b/docs/index.html
deleted file mode 100644
index 1ffe290..0000000
--- a/docs/index.html
+++ /dev/null
@@ -1,84 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Types
-Generator uint value : (State uint) -> (Generation uint value)A psuedorandom value generator
-Generation uint value :
- {
- value : value,
- state : (State uint)
- }A psuedorandom value, paired with its Generator
's output state (for chaining)
-Internal state for Generator
s
-State
constructors
-seed : U32 -> (State U32)Construct a "seed"
-A "seed" is an initial State
for Generator
s.
-This is an alias for seed32
.
-seedVariant : U32, U32 -> (State U32)Construct a specific "variant" of a "seed"
-A "seed" is an initial State
for Generator
s.
-A "variant" is a State
that specifies a c.updateIncrement
constant,
-to produce a sequence of internal value
s that shares no consecutive pairs
-with other variants of the same State
.
-Odd numbers are recommended for the update increment,
-to double the repetition period of sequences (by hitting odd values).
-Construct an initial State
from 8 bits of noise and a specific increment for updating
-seed16 : U16 -> (State U16)Construct an initial State
from 16 bits of noise
-Construct an initial State
from 16 bits of noise and a specific increment for updating
-seed32 : U32 -> (State U32)Construct an initial State
from 32 bits of noise
-Construct an initial State
from 32 bits of noise and a specific increment for updating
-
-next : (Generation uint *), (Generator uint value) -> (Generation uint value)Generate a new Generation
from an old Generation
's state
-step : (State uint), (Generator uint value) -> (Generation uint value)Generate a Generation
from a state
-Primitive Generator
constructors
-int : I32, I32 -> (Generator U32 I32)Construct a Generator
for 32-bit unsigned integers between two boundaries (inclusive)
-This is an alias for i32
.
-i8 : I8, I8 -> (Generator U8 I8)Construct a Generator
for 8-bit signed integers between two boundaries (inclusive)
-i16 : I16, I16 -> (Generator U16 I16)Construct a Generator
for 16-bit signed integers between two boundaries (inclusive)
-i32 : I32, I32 -> (Generator U32 I32)Construct a Generator
for 32-bit signed integers between two boundaries (inclusive)
-u8 : U8, U8 -> (Generator U8 U8)Construct a Generator
for 8-bit unsigned integers between two boundaries (inclusive)
-u16 : U16, U16 -> (Generator U16 U16)Construct a Generator
for 16-bit unsigned integers between two boundaries (inclusive)
-u32 : U32, U32 -> (Generator U32 U32)Construct a Generator
for 32-bit unsigned integers between two boundaries (inclusive)
-
-
-
-
-
-
diff --git a/docs/search.js b/docs/search.js
deleted file mode 100644
index fc0c041..0000000
--- a/docs/search.js
+++ /dev/null
@@ -1,44 +0,0 @@
-(() => {
- let sidebar = document.getElementById("sidebar-nav");
- let searchBox = document.getElementById("module-search");
-
- function search() {
- let text = searchBox.value.toLowerCase(); // Search is case-insensitive.
-
- if (text === "") {
- // Un-hide everything
- sidebar.querySelectorAll(".sidebar-entry a").forEach((entry) => entry.classList.remove("hidden"));
-
- // Re-hide all the sub-entries except for those of the current module
- let currentModuleName = document.querySelector('.module-name').textContent;
-
- sidebar.querySelectorAll(".sidebar-entry").forEach((entry) => {
- let entryName = entry.querySelector('.sidebar-module-link').textContent;
- if (currentModuleName === entryName) return;
- entry.querySelectorAll(".sidebar-sub-entries a").forEach((subEntry) => subEntry.classList.add("hidden"));
- })
- } else {
- // First, show/hide all the sub-entries within each module (top-level functions etc.)
- sidebar.querySelectorAll(".sidebar-sub-entries a").forEach((entry) => {
- if (entry.textContent.toLowerCase().includes(text)) {
- entry.classList.remove("hidden");
- } else {
- entry.classList.add("hidden");
- }
- });
-
- // Then, show/hide modules based on whether they match, or any of their sub-entries matched
- sidebar.querySelectorAll(".sidebar-module-link").forEach((entry) => {
- if (entry.textContent.toLowerCase().includes(text) || entry.parentNode.querySelectorAll(".sidebar-sub-entries a:not(.hidden)").length > 0) {
- entry.classList.remove("hidden");
- } else {
- entry.classList.add("hidden");
- }
- });
- }
- }
-
- searchBox.addEventListener("input", search);
-
- search();
-})();
\ No newline at end of file
diff --git a/docs/styles.css b/docs/styles.css
deleted file mode 100644
index 1c65d6a..0000000
--- a/docs/styles.css
+++ /dev/null
@@ -1,521 +0,0 @@
-:root {
- --link-color: #612bde;
- --code-link-color: #5721d4;
- --text-color: #333333;
- --code-color: #222222;
- --code-bg-color: #eeeeee;
- --body-bg-color: #fdfdfd;
- --border-color: #e9e9e9;
- --faded-color: #4c4c4c;
- --font-sans: -apple-system, BlinkMacSystemFont, Roboto, Helvetica, Arial, sans-serif;
- --font-mono: SFMono-Regular, Consolas, "Liberation Mono", Menlo, Courier, monospace;
- --top-header-height: 67px;
- --sidebar-width: 280px;
- --top-bar-bg: #8257e5;
- --top-bar-fg: #ffffff;
- --nav-link-hover-color: #000000;
-}
-
-a {
- color: #972395;
-}
-
-.logo {
- padding: 2px 8px;
-}
-
-.logo svg {
- height: 48px;
- width: 48px;
- fill: var(--top-bar-fg);
-}
-
-.logo:hover {
- text-decoration: none;
-}
-
-.logo svg:hover {
- fill: var(--nav-link-hover-color);
-}
-
-.pkg-full-name {
- color: var(--text-color);
- display: flex;
- align-items: center;
- font-size: 32px;
- margin: 0 8px;
- font-weight: normal;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- height: 100%;
-}
-
-.entry-name {
- white-space: pre-wrap;
-}
-
-.pkg-full-name a {
- padding-top: 12px;
- padding-bottom: 16px;
-}
-
-a {
- text-decoration: none;
-}
-
-a:hover, a:hover code {
- text-decoration: underline;
-}
-
-.pkg-and-logo {
- min-width: 0; /* necessary for text-overflow: ellipsis to work in descendants */
- display: flex;
- align-items: center;
- height: 100%;
- background-color: var(--top-bar-bg);
-}
-
-.pkg-and-logo a, .pkg-and-logo a:visited {
- color: var(--top-bar-fg);
-}
-
-.pkg-and-logo a:hover {
- color: var(--nav-link-hover-color);
- text-decoration: none;
-}
-
-.search-button {
- flex-shrink: 0; /* always shrink the package name before these; they have a relatively constrained length */
- padding: 12px 18px;
- margin-right: 42px;
- display: none; /* only show this in the mobile view */
-}
-
-.version {
- padding: 18px 10px;
- min-width: 48px;
- margin-right: 8px;
-}
-
-body {
- display: grid;
- grid-template-columns: [before-sidebar] 1fr [sidebar] var(--sidebar-width) [main-content] fit-content(calc(1280px - var(--sidebar-width))) [end] 1fr;
- grid-template-rows: [top-header] var(--top-header-height) [above-footer] auto [footer] auto;
- box-sizing: border-box;
- margin: 0;
- padding: 0;
- font-family: var(--font-sans);
- color: var(--text-color);
- background-color: var(--body-bg-color);
-}
-
-main {
- grid-column-start: main-content;
- grid-column-end: main-content;
- grid-row-start: above-footer;
- grid-row-end: above-footer;
- box-sizing: border-box;
- position: relative;
- font-size: 18px;
- line-height: 1.85em;
- margin-top: 2px;
- padding: 48px;
-
- min-width: 0; /* necessary for text-overflow: ellipsis to work in descendants */
-}
-
-#sidebar-nav {
- grid-column-start: sidebar;
- grid-column-end: sidebar;
- grid-row-start: above-footer;
- grid-row-end: above-footer;
- position: relative;
- display: flex;
- flex-direction: column;
- box-sizing: border-box;
- padding-left: 56px;
- padding-top: 6px;
- width: 100%;
-}
-
-.top-header-extension {
- grid-column-start: before-sidebar;
- grid-column-end: sidebar;
- grid-row-start: top-header;
- grid-row-end: top-header;
- background-color: var(--top-bar-bg);
-}
-
-.top-header {
- grid-column-start: sidebar;
- grid-column-end: end;
- grid-row-start: top-header;
- grid-row-end: top-header;
- display: flex;
- flex-direction: row;
- align-items: center;
- flex-wrap: nowrap;
- box-sizing: border-box;
- font-family: var(--font-sans);
- font-size: 24px;
- height: 100%;
- min-width: 0; /* necessary for text-overflow: ellipsis to work in descendants */
-}
-
-.top-header-triangle {
- /* This used to be a clip-path, but Firefox on Android (at least as of early 2020)
- * rendered the page extremely slowly in that version. With this approach it's super fast.
- */
- width: 0;
- height: 0;
- border-style: solid;
- border-width: var(--top-header-height) 0 0 48px;
- border-color: transparent transparent transparent var(--top-bar-bg);
-}
-
-p {
- overflow-wrap: break-word;
- margin: 24px 0;
-}
-
-footer {
- grid-column-start: main-content;
- grid-column-end: main-content;
- grid-row-start: footer;
- grid-row-end: footer;
- max-width: var(--main-content-max-width);
- font-size: 14px;
- box-sizing: border-box;
- padding: 16px;
-}
-
-footer p {
- display: inline-block;
- margin-top: 0;
- margin-bottom: 8px;
-}
-
-.content {
- box-sizing: border-box;
- display: flex;
- flex-direction: row;
- justify-content: space-between;
-}
-
-.sidebar-entry ul {
- list-style-type: none;
- margin: 0;
-}
-
-.sidebar-entry a {
- box-sizing: border-box;
- min-height: 48px;
- min-width: 48px;
- padding: 12px 16px;
- font-family: var(--font-mono);
-}
-
-.sidebar-sub-entries a {
- display: block;
- line-height: 24px;
- width: 100%;
- overflow: hidden;
- text-overflow: ellipsis;
- padding-left: 36px;
-}
-
-.module-name {
- font-size: 56px;
- line-height: 1em;
- font-family: var(--font-mono);
- font-weight: bold;
- margin-top: 18px;
- margin-bottom: 48px;
-}
-
-.module-name a, .module-name a:visited {
- color: inherit;
-}
-
-.sidebar-module-link {
- box-sizing: border-box;
- font-size: 18px;
- line-height: 24px;
- font-family: var(--font-mono);
- font-weight: bold;
- display: block;
- width: 100%;
- padding: 8px 0;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
-}
-
-a, a:visited {
- color: var(--link-color);
-}
-
-h3 {
- font-size: 32px;
- margin: 48px 0 24px 0;
-}
-
-h4 {
- font-size: 24px;
-}
-
-.type-def {
- font-size: 24px;
- color: var(--link-color);
-}
-
-.code-snippet {
- padding: 12px 16px;
- display: block;
- box-sizing: border-box;
- font-family: var(--font-mono);
- background-color: var(--code-bg-color);
-}
-
-code {
- font-family: var(--font-mono);
- color: var(--code-color);
- background-color: var(--code-bg-color);
- display: inline-block;
-}
-
-code a, a code {
- text-decoration: none;
- color: var(--code-link-color);
- background: none;
- padding: 0;
-}
-
-code a:visited, a:visited code {
- color: var(--code-link-color);
-}
-
-pre {
- margin: 36px 0;
- padding: 8px;
- box-sizing: border-box;
- background-color: var(--code-bg-color);
- overflow-x: auto;
-}
-
-.hidden {
- /* Use !important to win all specificity fights. */
- display: none !important;
-}
-
-.syntax-number {
- color: #60B7BF;
-}
-.syntax-string {
- color:#F7577C;
-}
-
-.syntax-bracket {
- color:#FF335F;
-}
-.syntax-closure-dash,
-.syntax-closure-arrow,
-.syntax-operator
-{
- color: #ffffff;
-}
-.syntax-comma {
- color: #9573E6;
-}
-.syntax-comment {
- color: #ff0000;
-}
-
-#module-search:placeholder-shown {
- padding: 0;
- opacity: 0;
- height: 0;
-}
-
-#module-search, #module-search:focus {
- opacity: 1;
- padding: 12px 16px;
- height: 48px;
-}
-
-/* Show the "Search" label link when the text input has a placeholder */
-#module-search:placeholder-shown + #search-link {
- display: flex;
-}
-
-/* Hide the "Search" label link when the text input has focus */
-#module-search:focus + #search-link {
- display: none;
-}
-
-#module-search {
- display: block;
- box-sizing: border-box;
- background-color: var(--code-bg-color);
- width: 100%;
- box-sizing: border-box;
- font-size: 18px;
- line-height: 18px;
- margin-top: 6px;
- border: none;
- color: var(--faded-color);
- background-color: var(--code-bg-color);
- font-family: var(--font-serif);
-}
-
-#module-search::placeholder {
- color: var(--faded-color);
- opacity: 1;
-}
-
-#search-link {
- box-sizing: border-box;
- display: none;
- align-items: center;
- font-size: 18px;
- line-height: 18px;
- padding: 12px 16px;
- height: 48px;
- cursor: pointer;
- color: var(--link-color);
-}
-
-#search-link:hover {
- text-decoration: underline;
-}
-
-@media (prefers-color-scheme: dark) {
- :root {
- --body-bg-color: #303030;
- --code-bg-color: #393939;
- --border-color: #555555;
- --code-color: #eeeeee;
- --text-color: #cccccc;
- --logo-solid: #777777;
- --faded-color: #bbbbbb;
- --link-color: #c5a8ff;
- --code-link-color: #b894ff;
- --top-bar-bg: #6845b9;
- --top-bar-fg: #eeeeee;
- }
-
- html {
- scrollbar-color: #444444 #2f2f2f;
- }
-}
-
-@media only screen and (max-device-width: 480px) and (orientation: portrait) {
- .search-button {
- display: block; /* This is only visible in mobile. */
- }
-
- .top-header {
- justify-content: space-between;
- width: auto;
- }
-
- .pkg-full-name {
- margin-left: 8px;
- margin-right: 12px;
- font-size: 24px;
- padding-bottom: 14px;
- }
-
- .pkg-full-name a {
- vertical-align: middle;
- padding: 18px 0;
- }
-
- .logo {
- padding-left: 2px;
- width: 50px;
- height: 54px;
- }
-
- .version {
- margin: 0;
- font-weight: normal;
- font-size: 18px;
- padding-bottom: 16px;
- }
-
- .module-name {
- font-size: 36px;
- margin-top: 8px;
- margin-bottom: 8px;
- max-width: calc(100% - 18px);
- overflow: hidden;
- text-overflow: ellipsis;
- }
-
- main {
- grid-column-start: none;
- grid-column-end: none;
- grid-row-start: above-footer;
- grid-row-end: above-footer;
- padding: 18px;
- font-size: 16px;
- }
-
- #sidebar-nav {
- grid-column-start: none;
- grid-column-end: none;
- grid-row-start: sidebar;
- grid-row-end: sidebar;
- margin-top: 0;
- padding-left: 0;
- width: auto;
- }
-
- #sidebar-heading {
- font-size: 24px;
- margin: 16px;
- }
-
- h3 {
- font-size: 18px;
- margin: 0;
- padding: 0;
- }
-
- h4 {
- font-size: 16px;
- }
-
- body {
- grid-template-columns: auto;
- grid-template-rows: [top-header] var(--top-header-height) [before-sidebar] auto [sidebar] auto [above-footer] auto [footer] auto;
-/* [before-sidebar] 1fr [sidebar] var(--sidebar-width) [main-content] fit-content(calc(1280px - var(--sidebar-width))) [end] 1fr; */
-
- margin: 0;
- min-width: 320px;
- max-width: 100%;
- }
-
- .top-header-triangle {
- display: none;
- }
-
- .pkg-and-logo {
- width: 100%;
- }
-
- .pkg-full-name {
- flex-grow: 1;
- }
-
- .pkg-full-name a {
- padding-top: 24px;
- padding-bottom: 12px;
- }
-}
-
-h1 {
- margin-top: 60px;
-}
diff --git a/example_simple.roc b/example_simple.roc
deleted file mode 100644
index e34e3ab..0000000
--- a/example_simple.roc
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/usr/bin/env roc
-
-app "example_simple"
- packages { pf: "./roc/examples/interactive/cli-platform/main.roc" }
- imports [ pf.Stdout.{ line }, pf.Task.{ await }, Random ]
- provides [ main ] to pf
-
-
-main =
-
- state = Random.seed 42 # `state` stores the "randomness", initialized by the user/platform.
- int = Random.int 0 100 # `int` generates values from 0-100 (inclusive) and updates state.
- x = int state # x == { value: 9, state: { value: -60952905, ... } }
- y = x |> Random.next int # y == { value: 61, state: { value: 1561666408, ... } }
-
- _ <- await (line (Num.toStr x.value |> \s -> "x: \(s)")) # This will print `x: 74`.
- _ <- await (line (Num.toStr y.value |> \s -> "y: \(s)")) # This will print `x: 96`.
- line "These values will be the same on every run, because we use a constant seed (42)."
diff --git a/example_complex.roc b/examples/complex.roc
similarity index 80%
rename from example_complex.roc
rename to examples/complex.roc
index 1869c68..35ec5b7 100644
--- a/example_complex.roc
+++ b/examples/complex.roc
@@ -1,8 +1,11 @@
-#!/usr/bin/env roc
-
+# !/usr/bin/env roc
app "example_complex"
- packages { pf: "roc/examples/interactive/cli-platform/main.roc" }
- imports [pf.Stdout.{ line }, pf.Task.{ await }, Random]
+ packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.3.1/97mY3sUwo433-pcnEQUlMhn-sWiIf_J9bPhcAFZoqY4.tar.br" }
+ imports [
+ pf.Stdout.{ line },
+ pf.Task.{ await },
+ Random,
+ ]
provides [main] to pf
main =
@@ -17,11 +20,14 @@ main =
_ <- await (line (Num.toStr b.value.y |> \s -> "b.y == 78 == \(s)"))
_ <- await (line (Num.toStr b.value.z |> \s -> "b.z == -64 == \(s)"))
_ <- await (line (Num.toStr b.value.t |> \s -> "b.t == -20 == \(s)"))
+
line "These values will be the same on every run, because we use a constant seed."
# Complex `Generator`s can be created by chaining primitive `Generator`s.
-Point a : { x : a, y : a, z : a, t : a }
-point : Random.Generator (Point I32) (Random.State U32)
+# Point a : { x : a, y : a, z : a, t : a }
+
+# TODO fix this type annotation
+# point : Random.Generator (Point I32) (Random.State U32)
point = \state ->
min = -100
max = 100
diff --git a/example_digits.roc b/examples/digits.roc
similarity index 90%
rename from example_digits.roc
rename to examples/digits.roc
index 2f02e16..71a2051 100644
--- a/example_digits.roc
+++ b/examples/digits.roc
@@ -1,10 +1,8 @@
-#!/usr/bin/env roc
-
+# !/usr/bin/env roc
app "example_digits"
- packages { pf: "./roc/examples/interactive/cli-platform" }
- imports [ pf.Stdout.{ line }, pf.Task.{ await }, Random ]
- provides [ main ] to pf
-
+ packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.3.1/97mY3sUwo433-pcnEQUlMhn-sWiIf_J9bPhcAFZoqY4.tar.br" }
+ imports [pf.Stdout.{ line }, pf.Task.{ await }, Random]
+ provides [main] to pf
main =
diff --git a/examples/simple.roc b/examples/simple.roc
new file mode 100644
index 0000000..109d428
--- /dev/null
+++ b/examples/simple.roc
@@ -0,0 +1,35 @@
+# !/usr/bin/env roc
+app "example_simple"
+ packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.3.1/97mY3sUwo433-pcnEQUlMhn-sWiIf_J9bPhcAFZoqY4.tar.br" }
+ imports [
+ pf.Stdout,
+ Random,
+ ]
+ provides [main] to pf
+
+main =
+
+ # Initialise "randomness"
+ initialSeed = Random.seed16 42
+
+ # Create a generator for values from 0-100 (inclusive)
+ u16 = Random.u16 0 100
+
+ # Create a list of random numbers
+ result =
+ List.range { start: At 1, end: At 1000 }
+ |> List.walk { seed: initialSeed, numbers: [] } \state, _ ->
+
+ random = u16 state.seed
+ seed = random.state
+ numbers = List.append state.numbers random.value
+
+ { seed, numbers }
+
+ # Format as a string
+ numbersListStr =
+ result.numbers
+ |> List.map Num.toStr
+ |> Str.joinWith ","
+
+ Stdout.line "Random numbers are: \(numbersListStr)"
diff --git a/Random.roc b/package/Random.roc
similarity index 66%
rename from Random.roc
rename to package/Random.roc
index 6bcf10b..1136048 100644
--- a/Random.roc
+++ b/package/Random.roc
@@ -1,3 +1,13 @@
+## ## PCG algorithms, constants, and wrappers
+##
+## Based on this paper [PCG: A Family of Simple Fast Space-Efficient Statistically Good Algorithms for Random Number Generation](https://www.pcg-random.org/pdf/hmc-cs-2014-0905.pdf)
+## and this C++ header: [pcg_variants.h](https://github.com/imneme/pcg-c/blob/master/include/pcg_variants.h)
+##
+## ## Abbreviations:
+## - M = Multiplication (see section 6.3.4 on page 45 in the paper)
+## - PCG = Permuted Congruential Generator
+## - RXS = Random XorShift (see section 5.5.1 on page 36 in the paper)
+## - XS = XorShift (see section 5.5 on page 34 in the paper)
interface Random
exposes [
Generation,
@@ -23,9 +33,6 @@ interface Random
]
imports []
-
-## # Types
-
## A psuedorandom value generator
Generator uint value : State uint -> Generation uint value
@@ -34,16 +41,15 @@ Generation uint value : { value : value, state : State uint }
## Internal state for [Generator]s
State uint := { s : uint, c : AlgorithmConstants uint }
+
AlgorithmConstants uint : {
permuteMultiplier : uint,
permuteRandomXorShift : uint,
permuteRandomXorShiftIncrement : uint,
permuteXorShift : uint,
updateIncrement : uint,
- updateMultiplier : uint }
-
-
-## # [State] constructors
+ updateMultiplier : uint,
+}
## Construct a "seed"
##
@@ -75,15 +81,16 @@ seed8 = \s -> seed8Variant s defaultU8UpdateIncrement
## Construct an initial [State] from 8 bits of noise and a specific increment for updating
seed8Variant : U8, U8 -> State U8
seed8Variant = \s, uI ->
- @State {
- s,
- c: {
- permuteMultiplier: defaultU8PermuteMultiplier,
- permuteRandomXorShift: defaultU8PermuteRandomXorShift,
- permuteRandomXorShiftIncrement: defaultU8PermuteRandomXorShiftIncrement,
- permuteXorShift: defaultU8PermuteXorShift,
- updateIncrement: uI,
- updateMultiplier: defaultU8UpdateMultiplier } }
+ c = {
+ permuteMultiplier: defaultU8PermuteMultiplier,
+ permuteRandomXorShift: defaultU8PermuteRandomXorShift,
+ permuteRandomXorShiftIncrement: defaultU8PermuteRandomXorShiftIncrement,
+ permuteXorShift: defaultU8PermuteXorShift,
+ updateIncrement: uI,
+ updateMultiplier: defaultU8UpdateMultiplier,
+ }
+
+ @State { s, c }
## Construct an initial [State] from 16 bits of noise
seed16 : U16 -> State U16
@@ -92,15 +99,16 @@ seed16 = \s -> seed16Variant s defaultU16UpdateIncrement
## Construct an initial [State] from 16 bits of noise and a specific increment for updating
seed16Variant : U16, U16 -> State U16
seed16Variant = \s, uI ->
- @State {
- s,
- c: {
- permuteMultiplier: defaultU16PermuteMultiplier,
- permuteRandomXorShift: defaultU16PermuteRandomXorShift,
- permuteRandomXorShiftIncrement: defaultU16PermuteRandomXorShiftIncrement,
- permuteXorShift: defaultU16PermuteXorShift,
- updateIncrement: uI,
- updateMultiplier: defaultU16UpdateMultiplier } }
+ c = {
+ permuteMultiplier: defaultU16PermuteMultiplier,
+ permuteRandomXorShift: defaultU16PermuteRandomXorShift,
+ permuteRandomXorShiftIncrement: defaultU16PermuteRandomXorShiftIncrement,
+ permuteXorShift: defaultU16PermuteXorShift,
+ updateIncrement: uI,
+ updateMultiplier: defaultU16UpdateMultiplier,
+ }
+
+ @State { s, c }
## Construct an initial [State] from 32 bits of noise
seed32 : U32 -> State U32
@@ -109,18 +117,16 @@ seed32 = \s -> seed32Variant s defaultU32UpdateIncrement
## Construct an initial [State] from 32 bits of noise and a specific increment for updating
seed32Variant : U32, U32 -> State U32
seed32Variant = \s, uI ->
- @State {
- s,
- c: {
- permuteMultiplier: defaultU32PermuteMultiplier,
- permuteRandomXorShift: defaultU32PermuteRandomXorShift,
- permuteRandomXorShiftIncrement: defaultU32PermuteRandomXorShiftIncrement,
- permuteXorShift: defaultU32PermuteXorShift,
- updateIncrement: uI,
- updateMultiplier: defaultU32UpdateMultiplier } }
+ c = {
+ permuteMultiplier: defaultU32PermuteMultiplier,
+ permuteRandomXorShift: defaultU32PermuteRandomXorShift,
+ permuteRandomXorShiftIncrement: defaultU32PermuteRandomXorShiftIncrement,
+ permuteXorShift: defaultU32PermuteXorShift,
+ updateIncrement: uI,
+ updateMultiplier: defaultU32UpdateMultiplier,
+ }
-
-## # [Generator] helpers
+ @State { s, c }
## Generate a new [Generation] from an old [Generation]'s state
next : Generation uint *, Generator uint value -> Generation uint value
@@ -130,9 +136,6 @@ next = \x, g -> g x.state
step : State uint, Generator uint value -> Generation uint value
step = \s, g -> g s
-
-## # Primitive [Generator] constructors
-
## Construct a [Generator] for 32-bit unsigned integers between two boundaries (inclusive)
##
## This is an alias for [i32].
@@ -142,7 +145,7 @@ int = i32
## Construct a [Generator] for 8-bit signed integers between two boundaries (inclusive)
i8 : I8, I8 -> Generator U8 I8
i8 = \x, y ->
- Pair minimum maximum = sort x y
+ (minimum, maximum) = sort x y
# TODO: Remove these `I64` dependencies.
range = maximum - minimum + 1 |> Num.toI64
\state ->
@@ -154,7 +157,7 @@ i8 = \x, y ->
## Construct a [Generator] for 16-bit signed integers between two boundaries (inclusive)
i16 : I16, I16 -> Generator U16 I16
i16 = \x, y ->
- Pair minimum maximum = sort x y
+ (minimum, maximum) = sort x y
# TODO: Remove these `I64` dependencies.
range = maximum - minimum + 1 |> Num.toI64
\state ->
@@ -166,7 +169,7 @@ i16 = \x, y ->
## Construct a [Generator] for 32-bit signed integers between two boundaries (inclusive)
i32 : I32, I32 -> Generator U32 I32
i32 = \x, y ->
- Pair minimum maximum = sort x y
+ (minimum, maximum) = sort x y
# TODO: Remove these `I64` dependencies.
range = maximum - minimum + 1 |> Num.toI64
\state ->
@@ -187,16 +190,19 @@ u16 = \x, y -> betweenUnsigned x y
u32 : U32, U32 -> Generator U32 U32
u32 = \x, y -> betweenUnsigned x y
-
-#### Helpers for the above constructors
+# Helpers for the above constructors -------------------------------------------
betweenUnsigned = \x, y ->
- Pair minimum maximum = sort x y
+ (minimum, maximum) = sort x y
range = maximum - minimum + 1
+
\s ->
# TODO: Analyze this. The mod-ing might be biased towards a smaller offset!
+
value = minimum + (permute s) % range
- { value, state: update s }
+ state = update s
+
+ { value, state }
mapToI8 : U8 -> I8
mapToI8 = \x ->
@@ -224,20 +230,9 @@ mapToI32 = \x ->
sort = \x, y ->
if x < y then
- Pair x y
+ (x, y)
else
- Pair y x
-
-
-#### PCG algorithms, constants, and wrappers
-#
-# Based on this paper: https://www.pcg-random.org/pdf/hmc-cs-2014-0905.pdf
-# Based on this C++ header: https://github.com/imneme/pcg-c/blob/master/include/pcg_variants.h
-# Abbreviations:
-# M = Multiplication (see section 6.3.4 on page 45 in the paper)
-# PCG = Permuted Congruential Generator
-# RXS = Random XorShift (see section 5.5.1 on page 36 in the paper)
-# XS = XorShift (see section 5.5 on page 34 in the paper)
+ (y, x)
# See `RXS M XS` constants (line 168?)
# and `_DEFAULT_` constants (line 276?)
@@ -276,23 +271,93 @@ defaultU32UpdateMultiplier = 747_796_405
# defaultU128UpdateMultiplier = (Num.shiftLeftBy 64 2_549_297_995_355_413_924) + 4_865_540_595_714_422_341
# See `pcg_output_rxs_m_xs_8_8` (on line 170?) in the PCG C++ header (see link above).
+permute : State (Int uint) -> Int uint
permute = \@State { s, c } ->
pcgRxsMXs s c.permuteRandomXorShift c.permuteRandomXorShiftIncrement c.permuteMultiplier c.permuteXorShift
# See section 6.3.4 on page 45 in the PCG paper (see link above).
+pcgRxsMXs : Int uint, Int uint, Int uint, Int uint, Int uint -> Int uint
pcgRxsMXs = \state, randomXorShift, randomXorShiftIncrement, multiplier, xorShift ->
- partial = Num.mulWrap multiplier (
- Num.bitwiseXor state (
- Num.shiftRightZfBy (
- Num.addWrap (Num.shiftRightZfBy randomXorShift state) randomXorShiftIncrement
- ) state
- ))
- Num.bitwiseXor partial (Num.shiftRightZfBy xorShift partial)
+
+ inner =
+ randomXorShift
+ |> Num.shiftRightZfBy (Num.intCast state)
+ |> Num.addWrap randomXorShiftIncrement
+ |> Num.shiftRightZfBy (Num.intCast state)
+
+ partial =
+ state
+ |> Num.bitwiseXor inner
+ |> Num.mulWrap multiplier
+
+ Num.bitwiseXor partial (Num.shiftRightZfBy xorShift (Num.intCast partial))
# See section 4.1 on page 20 in the PCG paper (see link above).
+pcgStep : Int uint, Int uint, Int uint -> Int uint
pcgStep = \state, multiplier, increment ->
- Num.addWrap (Num.mulWrap state multiplier) increment
+ state
+ |> Num.mulWrap multiplier
+ |> Num.addWrap increment
# See `pcg_oneseq_8_step_r` (line 409?) in the PCG C++ header (see link above).
+update : State (Int uint) -> State (Int uint)
update = \@State { s, c } ->
- @State { s: pcgStep s c.updateMultiplier c.updateIncrement, c }
+
+ sNew : Int uint
+ sNew = pcgStep s c.updateMultiplier c.updateIncrement
+
+ @State { s: sNew, c }
+
+# Test U8 generation
+# TODO confirm this is the right value for this seed
+expect
+ testGenerator = u8 0 250
+ testSeed = seed8 123
+ actual = testGenerator testSeed
+ expected = 67u8
+ actual.value == expected
+
+# Test U16 generation
+# TODO confirm this is the right value for this seed
+expect
+ testGenerator = u16 0 250
+ testSeed = seed16 123
+ actual = testGenerator testSeed
+ expected = 183u16
+ actual.value == expected
+
+# Test U32 generation
+# TODO confirm this is the right value for this seed
+expect
+ testGenerator = u32 0 250
+ testSeed = seed32 123
+ actual = testGenerator testSeed
+ expected = 143u32
+ actual.value == expected
+
+# Test I8 generation
+# TODO confirm this is the right value for this seed
+expect
+ testGenerator = i8 0 9
+ testSeed = seed8 6
+ actual = testGenerator testSeed
+ expected = 2i8
+ actual.value == expected
+
+# Test I16 generation
+# TODO confirm this is the right value for this seed
+expect
+ testGenerator = i16 0 9
+ testSeed = seed16 6
+ actual = testGenerator testSeed
+ expected = 4i16
+ actual.value == expected
+
+# Test I32 generation
+# TODO confirm this is the right value for this seed
+expect
+ testGenerator = i32 0 9
+ testSeed = seed32 6
+ actual = testGenerator testSeed
+ expected = 2i32
+ actual.value == expected
diff --git a/package/main.roc b/package/main.roc
new file mode 100644
index 0000000..0614d18
--- /dev/null
+++ b/package/main.roc
@@ -0,0 +1,5 @@
+package "random"
+ exposes [
+ Random,
+ ]
+ packages {}