From cf69d0ce580ab6fe2ccd27c074b70fc3abac8c28 Mon Sep 17 00:00:00 2001 From: Jeremie Gillet Date: Sat, 14 Oct 2023 10:42:04 +0900 Subject: [PATCH 01/18] Add concept exercise --- config.json | 21 ++ exercises/concept/maze-maker/.docs/hints.md | 46 +++++ .../concept/maze-maker/.docs/instructions.md | 53 +++++ .../maze-maker/.docs/introduction.md.tpl | 3 + .../concept/maze-maker/.meta/Exemplar.elm | 56 ++++++ .../concept/maze-maker/.meta/config.json | 20 ++ exercises/concept/maze-maker/.meta/design.md | 48 +++++ exercises/concept/maze-maker/elm.json | 29 +++ .../concept/maze-maker/src/MazeMaker.elm | 45 +++++ .../maze-maker/src/MazeMakerSupport.elm | 62 ++++++ exercises/concept/maze-maker/tests/Tests.elm | 187 ++++++++++++++++++ 11 files changed, 570 insertions(+) create mode 100644 exercises/concept/maze-maker/.docs/hints.md create mode 100644 exercises/concept/maze-maker/.docs/instructions.md create mode 100644 exercises/concept/maze-maker/.docs/introduction.md.tpl create mode 100644 exercises/concept/maze-maker/.meta/Exemplar.elm create mode 100644 exercises/concept/maze-maker/.meta/config.json create mode 100644 exercises/concept/maze-maker/.meta/design.md create mode 100644 exercises/concept/maze-maker/elm.json create mode 100644 exercises/concept/maze-maker/src/MazeMaker.elm create mode 100644 exercises/concept/maze-maker/src/MazeMakerSupport.elm create mode 100644 exercises/concept/maze-maker/tests/Tests.elm diff --git a/config.json b/config.json index 15319442..1460c438 100644 --- a/config.json +++ b/config.json @@ -179,6 +179,11 @@ "uuid": "563a82fc-bb43-4866-a3d2-fd35eee5efa5", "slug": "strings", "name": "Strings" + }, + { + "slug": "random", + "name": "Random", + "uuid": "357ffb6e-5a73-49b6-8b69-98ecd3c74c33" } ], "exercises": { @@ -457,6 +462,22 @@ "maybe" ], "status": "beta" + }, + { + "slug": "maze-maker", + "name": "Maze Maker", + "uuid": "ce133a47-bff5-47ad-b6b5-df60b1627ebc", + "concepts": [ + "random" + ], + "prerequisites": [ + "basics-1", + "basics-2", + "tuples", + "lists", + "custom-types" + ], + "status": "beta" } ], "practice": [ diff --git a/exercises/concept/maze-maker/.docs/hints.md b/exercises/concept/maze-maker/.docs/hints.md new file mode 100644 index 00000000..23bbb8a1 --- /dev/null +++ b/exercises/concept/maze-maker/.docs/hints.md @@ -0,0 +1,46 @@ +# Hints + +## General + +- The documentation for the `Random` package can be found [here][random-module] + +## 1. Start with the end + +- Use the [`Random.constant`][random-constant] function + +## 2. You can't catch flies with vinegar + +- Use the [`Random.uniform`][random-uniform] function for generating the treasures +- Use the [`Random.map`][random-map] function for wrapping the treasure + +## 3. Branching out + +- Use the [`Random.int`][random-int] function for generating a value between 2 and 4 +- You can access this value to create a `Branch` with the [`Random.andThen`][random-andThen] function +- Use the [`Random.list`][random-list] function to generate the branches +- Use the [`Random.map`][random-map] function for wrapping the branches + +## 4. Amazing mazes + +- Use the [`Random.weighted`][random-weighted] function for generating an uneven distribution of outcomes +- Use the [`Random.lazy`][random-lazy] function for using a generator recursively +- Read more about defining recursive data structures [here][bad-recursion] +- If you end up defining a nested `Generator (Generator Maze)` generator, you can use the [`Random.andThen`][random-andThen] function to flatten it + +## 5. We have to go deeper + +- Use the [`Random.uniform`][random-uniform] function for generating the dead ends or rooms in on depth `0` +- If you end up defining a nested `Generator (Generator Maze)` generator, you can use the [`Random.andThen`][random-andThen] function to flatten it +- Use the [`Random.lazy`][random-lazy] function for using a generator recursively +- Read more about defining recursive data structures [here][bad-recursion] + +[random-module]: https://package.elm-lang.org/packages/elm/random/latest/ +[random-constant]: https://package.elm-lang.org/packages/elm/random/latest/Random#constant +[random-uniform]: https://package.elm-lang.org/packages/elm/random/latest/Random#uniform +[random-map]: https://package.elm-lang.org/packages/elm/random/latest/Random#map +[random-int]: https://package.elm-lang.org/packages/elm/random/latest/Random#int +[random-andThen]: https://package.elm-lang.org/packages/elm/random/latest/Random#andThen +[random-list]: https://package.elm-lang.org/packages/elm/random/latest/Random#list +[random-weighted]: https://package.elm-lang.org/packages/elm/random/latest/Random#weighted +[random-lazy]: https://package.elm-lang.org/packages/elm/random/latest/Random#lazy +[bad-recursion]: https://github.com/elm/compiler/blob/master/hints/bad-recursion.md diff --git a/exercises/concept/maze-maker/.docs/instructions.md b/exercises/concept/maze-maker/.docs/instructions.md new file mode 100644 index 00000000..fac4e919 --- /dev/null +++ b/exercises/concept/maze-maker/.docs/instructions.md @@ -0,0 +1,53 @@ +# Instructions + +You are a maze researcher, working to design a new generation of dungeons and adventuring grounds. + +You are a theoretician, which means you are not concerned with the physical layout of mazes (that is a task for maze engineers), but instead you care about their mathematical complexity: branching factor, maximum depth and such. + +You decide to approach your next project with a random generation approach. + +## 1. Start with the end + +Any good maze is mostly dead ends. + +Define the `deadend` generator so that it always generate the `DeadEnd` variant. + +## 2. You can't catch flies with vinegar + +Let's give adventurers a reason to venture in your mazes. + +Define the `treasure` generator so that it generates either `Gold`, `Diamond` or `Friendship` with equal probability. + +Then, define the `room` generator so that it wraps the values generated by `treasure` in the `Room` variant. + +## 3. Branching out + +And now for the heart and soul of a maze: branches. + +The `branch` generator receives a `Generator Maze` argument, which is a generator able to generate an arbitrary maze. + +Define the `branch` generator so that it generates a `Branch` variant containing several branches leading to mazes generated by its argument. + +The number of branches should be between 2 and 4 (any more than that and the maze engineer would get angry), determined randomly with equal probability. + +## 4. Amazing mazes + +Time to do business. + +Define the `maze` generator so that it generates a dead end 60% of the time, a treasure room 15% of the time and a branch 25% of the time. +The branches should generate new mazes using `maze` recursively. +Make sure to use `deadend`, `room` and `branch` in the generator. + +Each branch contains on average 3 mazes, but only 25% of these will branch out again, which is less than one, so your generated mazes are likely to eventually end. + +## 5. We have to go deeper + +Your `maze` is nice, but it has some issues: + +1. 75% of the generated values don't have any branch at all, hardly real mazes +2. Adventurers might find treasure rooms early in the maze and decide to stop exploring +3. While deep mazes can be generated, they are not very likely: a maze of depth of 3 has an approximate 3% chance of being generated, a maze of depth 10 a 0.2% chance (the depth of a maze is calculated with `MazeMakerSupport.mazeDepth`) + +Define the `mazeOfDepth n` generator so that it generates a maze of depth `n`. +Only the deepest level `0` should contain dead ends or treasure rooms (with equal probability), all other levels should be a branch containing mazes of depth `n - 1` generated recursively with `mazeOfDepth`. +Make sure to use `deadend`, `room` and `branch` in the generator. diff --git a/exercises/concept/maze-maker/.docs/introduction.md.tpl b/exercises/concept/maze-maker/.docs/introduction.md.tpl new file mode 100644 index 00000000..d3d95c63 --- /dev/null +++ b/exercises/concept/maze-maker/.docs/introduction.md.tpl @@ -0,0 +1,3 @@ +# Introduction + +%{concept: random} diff --git a/exercises/concept/maze-maker/.meta/Exemplar.elm b/exercises/concept/maze-maker/.meta/Exemplar.elm new file mode 100644 index 00000000..84ddcf18 --- /dev/null +++ b/exercises/concept/maze-maker/.meta/Exemplar.elm @@ -0,0 +1,56 @@ +module MazeMaker exposing (..) + +import Random exposing (Generator) + + +type Maze + = DeadEnd + | Room Treasure + | Branch (List Maze) + + +type Treasure + = Gold + | Diamond + | Friendship + + +deadend : Generator Maze +deadend = + Random.constant DeadEnd + + +treasure : Generator Treasure +treasure = + Random.uniform Friendship [ Gold, Diamond ] + + +room : Generator Maze +room = + Random.map Room treasure + + +branch : Generator Maze -> Generator Maze +branch mazeGenerator = + Random.int 2 4 + |> Random.andThen + (\n -> Random.list n mazeGenerator |> Random.map Branch) + + +maze : Generator Maze +maze = + Random.weighted + ( 0.6, deadend ) + [ ( 0.15, room ) + , ( 0.25, branch (Random.lazy (\_ -> maze)) ) + ] + |> Random.andThen identity + + +mazeOfDepth : Int -> Generator Maze +mazeOfDepth depth = + if depth == 0 then + Random.uniform deadend [ room ] |> Random.andThen identity + + else + branch (Random.lazy (\_ -> mazeOfDepth (depth - 1))) diff --git a/exercises/concept/maze-maker/.meta/config.json b/exercises/concept/maze-maker/.meta/config.json new file mode 100644 index 00000000..478d8ebd --- /dev/null +++ b/exercises/concept/maze-maker/.meta/config.json @@ -0,0 +1,20 @@ +{ + "authors": [ + "jiegillet" + ], + "files": { + "solution": [ + "src/MazeMaker.elm" + ], + "test": [ + "tests/Tests.elm" + ], + "exemplar": [ + ".meta/Exemplar.elm" + ], + "editor": [ + "src/MazeMakerSupport.elm" + ] + }, + "blurb": "Learn to generate random values in Elm" +} diff --git a/exercises/concept/maze-maker/.meta/design.md b/exercises/concept/maze-maker/.meta/design.md new file mode 100644 index 00000000..539d65a7 --- /dev/null +++ b/exercises/concept/maze-maker/.meta/design.md @@ -0,0 +1,48 @@ +# Design + +## Goal + +The students will learn to generate random values through the use of generators. + +## Learning objectives + +Students will be able to + +- Explain the general idea of a `Generator` +- Explain why a pure language can't use `math.random()` +- Use primitive generators: `int`, `float`, `uniform`, `weighted`, `constant` +- Combine them with data structures: `pair`, `list` +- Modify generators with `map`, `mapN`, `andThen` +- Know to use `lazy` for recursive applications +- Look up more helpers in `elm-community/random-extra` + +## Out of scope + +- Using `generate` (we can only mention because it returns a `Cmd msg`) +- Generate values: `Seed`, `step`, `initialSeed` (only mentioned, not used) +- `independentSeed` +- `maxInt`, `minTint` + +## Concepts + +The concepts this exercise unlock are: + +- random + +## Prerequisites + +- basics-1 +- basics-2 +- tuples +- lists +- custom-types + +## Analyzer + +Make sure that: + +- `room` uses `treasure` +- `maze` uses `deadend`, `branch` and `room` +- `maze` uses `maze` recursively +- `mazeOfDepth` uses `deadend`, `branch` and `room` +- `mazeOfDepth` uses `mazeOfDepth` recursively diff --git a/exercises/concept/maze-maker/elm.json b/exercises/concept/maze-maker/elm.json new file mode 100644 index 00000000..22c9e137 --- /dev/null +++ b/exercises/concept/maze-maker/elm.json @@ -0,0 +1,29 @@ +{ + "type": "application", + "source-directories": [ + "src" + ], + "elm-version": "0.19.1", + "dependencies": { + "direct": { + "elm/core": "1.0.5", + "elm/json": "1.1.3", + "elm/parser": "1.1.0", + "elm/random": "1.0.0", + "elm/regex": "1.0.0", + "elm/time": "1.0.0" + }, + "indirect": {} + }, + "test-dependencies": { + "direct": { + "elm-explorations/test": "2.1.0", + "rtfeldman/elm-iso8601-date-strings": "1.1.4" + }, + "indirect": { + "elm/bytes": "1.0.8", + "elm/html": "1.0.0", + "elm/virtual-dom": "1.0.3" + } + } +} diff --git a/exercises/concept/maze-maker/src/MazeMaker.elm b/exercises/concept/maze-maker/src/MazeMaker.elm new file mode 100644 index 00000000..1cc42a69 --- /dev/null +++ b/exercises/concept/maze-maker/src/MazeMaker.elm @@ -0,0 +1,45 @@ +module MazeMaker exposing (..) + +import Random exposing (Generator) + + +type Maze + = DeadEnd + | Room Treasure + | Branch (List Maze) + + +type Treasure + = Gold + | Diamond + | Friendship + + +deadend : Generator Maze +deadend = + Debug.todo "Please implement deadend" + + +treasure : Generator Treasure +treasure = + Debug.todo "Please implement treasure" + + +room : Generator Maze +room = + Debug.todo "Please implement room" + + +branch : Generator Maze -> Generator Maze +branch mazeGenerator = + Debug.todo "Please implement branch" + + +maze : Generator Maze +maze = + Debug.todo "Please implement maze" + + +mazeOfDepth : Int -> Generator Maze +mazeOfDepth depth = + Debug.todo "Please implement mazeOfDepth" diff --git a/exercises/concept/maze-maker/src/MazeMakerSupport.elm b/exercises/concept/maze-maker/src/MazeMakerSupport.elm new file mode 100644 index 00000000..5e9e51cb --- /dev/null +++ b/exercises/concept/maze-maker/src/MazeMakerSupport.elm @@ -0,0 +1,62 @@ +module MazeMakerSupport exposing (..) + +import MazeMaker exposing (Maze(..)) + + +isBranch : Maze -> Bool +isBranch maze = + case maze of + DeadEnd -> + False + + Room _ -> + False + + Branch _ -> + True + + +isRoom : Maze -> Bool +isRoom maze = + case maze of + DeadEnd -> + False + + Room _ -> + True + + Branch _ -> + False + + +branchNumber : Maze -> Int +branchNumber maze = + case maze of + DeadEnd -> + 0 + + Room _ -> + 0 + + Branch mazes -> + mazes + |> List.map branchNumber + |> List.sum + |> (+) (List.length mazes) + + +mazeDepth : Maze -> Int +mazeDepth maze = + case maze of + DeadEnd -> + 0 + + Room _ -> + 0 + + Branch mazes -> + mazes + |> List.map mazeDepth + |> List.maximum + |> Maybe.withDefault 0 + |> (+) 1 diff --git a/exercises/concept/maze-maker/tests/Tests.elm b/exercises/concept/maze-maker/tests/Tests.elm new file mode 100644 index 00000000..48b1221f --- /dev/null +++ b/exercises/concept/maze-maker/tests/Tests.elm @@ -0,0 +1,187 @@ +module Tests exposing (tests) + +import Expect +import Fuzz +import MazeMaker exposing (Maze(..), Treasure(..), branch) +import MazeMakerSupport +import Parser exposing (Step(..)) +import Random +import Test exposing (Test, describe, fuzz, fuzzWith) +import Test.Distribution + + +tests : Test +tests = + describe "MazeMaker" + [ describe "1" + [ fuzz (Fuzz.fromGenerator MazeMaker.deadend) + "deadend always generates a Deadend" + <| + \deadend -> deadend |> Expect.equal DeadEnd + ] + , describe "2" + [ fuzzWith + { runs = 100 + , distribution = + Test.expectDistribution + [ ( Test.Distribution.atLeast 30, "is Friendship", (==) Friendship ) + , ( Test.Distribution.atLeast 30, "is Gold", (==) Gold ) + , ( Test.Distribution.atLeast 30, "is Diamond", (==) Diamond ) + ] + } + (Fuzz.fromGenerator MazeMaker.treasure) + "treasure generates all three kinds of Treasure equally" + <| + \_ -> Expect.pass + , fuzzWith + { runs = 100 + , distribution = + Test.expectDistribution + [ ( Test.Distribution.atLeast 30, "is Room Friendship", (==) (Room Friendship) ) + , ( Test.Distribution.atLeast 30, "is Room Gold", (==) (Room Gold) ) + , ( Test.Distribution.atLeast 30, "is Room Diamond", (==) (Room Diamond) ) + ] + } + (Fuzz.fromGenerator MazeMaker.room) + "room generates a Room with all three kinds of Treasure equally represented" + <| + \_ -> Expect.pass + ] + , describe "3" + [ fuzz + (Fuzz.fromGenerator (MazeMaker.branch MazeMaker.deadend)) + "branch generates a Branch variant" + <| + \branch -> MazeMakerSupport.isBranch branch |> Expect.equal True + , fuzz + (Fuzz.fromGenerator (MazeMaker.branch MazeMaker.deadend)) + "branch generates branches with deadends" + <| + \branch -> + case branch of + Branch branches -> + List.all ((==) DeadEnd) branches |> Expect.equal True + + _ -> + Expect.fail "should only generate a Branch" + , fuzz + (Fuzz.fromGenerator (MazeMaker.branch MazeMaker.room)) + "branch generates branches with rooms" + <| + \branch -> + case branch of + Branch branches -> + List.all MazeMakerSupport.isRoom branches |> Expect.equal True + + _ -> + Expect.fail "should only generate a Branch" + , fuzz + (Fuzz.fromGenerator (MazeMaker.branch MazeMaker.deadend)) + "branch generates at least two branches" + <| + \branch -> MazeMakerSupport.branchNumber branch |> Expect.atLeast 2 + , fuzz + (Fuzz.fromGenerator (MazeMaker.branch MazeMaker.deadend)) + "branch generates at most four branches" + <| + \branch -> MazeMakerSupport.branchNumber branch |> Expect.atMost 4 + , fuzzWith + { runs = 100 + , distribution = + Test.expectDistribution + [ ( Test.Distribution.atLeast 30, "has 2 branches", (==) 2 ) + , ( Test.Distribution.atLeast 30, "has 3 branches", (==) 3 ) + , ( Test.Distribution.atLeast 30, "has 4 branches", (==) 4 ) + ] + } + (Fuzz.fromGenerator (MazeMaker.branch MazeMaker.deadend |> Random.map MazeMakerSupport.branchNumber)) + "branch generates 2, 3, or 4 branches equally" + <| + \_ -> Expect.pass + ] + , describe "4" + [ fuzzWith + { runs = 100 + , distribution = + Test.expectDistribution + [ ( Test.Distribution.atLeast 50, "is a deadend", (==) DeadEnd ) + , ( Test.Distribution.atLeast 10, "is a room", MazeMakerSupport.isRoom ) + , ( Test.Distribution.atLeast 20, "is a branch", MazeMakerSupport.isBranch ) + ] + } + (Fuzz.fromGenerator MazeMaker.maze) + "generates 60% or deadends, 15% of rooms and 25% of branches" + <| + \_ -> Expect.pass + , fuzzWith + { runs = 1000 + , distribution = + Test.expectDistribution + [ ( Test.Distribution.moreThanZero, "depth 0", \m -> MazeMakerSupport.mazeDepth m == 0 ) + , ( Test.Distribution.moreThanZero, "depth 1", \m -> MazeMakerSupport.mazeDepth m == 1 ) + , ( Test.Distribution.moreThanZero, "depth 2", \m -> MazeMakerSupport.mazeDepth m == 2 ) + , ( Test.Distribution.moreThanZero, "depth 3", \m -> MazeMakerSupport.mazeDepth m == 3 ) + ] + } + (Fuzz.fromGenerator MazeMaker.maze) + "can generates mazes of depth at least 3" + <| + \_ -> Expect.pass + ] + , describe "5" + [ fuzz (Fuzz.fromGenerator (MazeMaker.mazeOfDepth 0)) + "mazeOfDepth 0 generates a maze of depth 0" + <| + \maze -> MazeMakerSupport.mazeDepth maze |> Expect.equal 0 + , fuzz (Fuzz.fromGenerator (MazeMaker.mazeOfDepth 1)) + "mazeOfDepth 1 generates a maze of depth 1" + <| + \maze -> MazeMakerSupport.mazeDepth maze |> Expect.equal 1 + , fuzz (Fuzz.fromGenerator (MazeMaker.mazeOfDepth 5)) + "mazeOfDepth 5 generates a maze of depth 5" + <| + \maze -> MazeMakerSupport.mazeDepth maze |> Expect.equal 5 + , fuzz (Fuzz.fromGenerator (MazeMaker.mazeOfDepth 1)) + "mazeOfDepth 1 generates a maze with 2 to 4 rooms or deadends" + <| + \maze -> + case maze of + Branch branches -> + if List.all (MazeMakerSupport.isBranch >> not) branches then + List.length branches + |> Expect.all [ Expect.atLeast 2, Expect.atMost 4 ] + + else + Expect.fail "There should only be deadlines and rooms on depth 1 of 2" + + _ -> + Expect.fail "There should only be branches on depth 0 of 1" + , fuzz (Fuzz.fromGenerator (MazeMaker.mazeOfDepth 2)) + "mazeOfDepth 2 generates a maze with rooms and deadends at the bottom exclusively" + <| + \maze -> + let + branchContainsNoBranches branch = + case branch of + Branch bottomBranches -> + List.all (MazeMakerSupport.isBranch >> not) bottomBranches + + _ -> + False + in + case maze of + Branch branches -> + if List.all MazeMakerSupport.isBranch branches then + if List.all branchContainsNoBranches branches then + Expect.pass + + else + Expect.fail "There should only be deadends and rooms on depth 2 of 2" + + else + Expect.fail "There should only be branches on depth 1 of 2" + + _ -> + Expect.fail "There should only be branches on depth 0 of 2" + ] + ] From 526642fd39ed90232f1c293d53f476b86b225288 Mon Sep 17 00:00:00 2001 From: Jeremie Gillet Date: Sat, 14 Oct 2023 21:12:31 +0900 Subject: [PATCH 02/18] Add concept --- concepts/random/.meta/config.json | 6 ++ concepts/random/about.md | 128 ++++++++++++++++++++++++++++++ concepts/random/introduction.md | 125 +++++++++++++++++++++++++++++ concepts/random/links.json | 18 +++++ 4 files changed, 277 insertions(+) create mode 100644 concepts/random/.meta/config.json create mode 100644 concepts/random/about.md create mode 100644 concepts/random/introduction.md create mode 100644 concepts/random/links.json diff --git a/concepts/random/.meta/config.json b/concepts/random/.meta/config.json new file mode 100644 index 00000000..1c27dc85 --- /dev/null +++ b/concepts/random/.meta/config.json @@ -0,0 +1,6 @@ +{ + "blurb": "Learn how to generate random values in Elm", + "authors": [ + "jiegillet" + ] +} diff --git a/concepts/random/about.md b/concepts/random/about.md new file mode 100644 index 00000000..ffd0b6d1 --- /dev/null +++ b/concepts/random/about.md @@ -0,0 +1,128 @@ +# About + +In a pure functional language like Elm, a function called with the same arguments must always return the same value. +Therefore a function with the type signature `rand : Int` can only be implemented as (`rand = 4`)[xkcd-random], which does not bode well for generating random integers. + +So how do we generate random values in Elm? +We split the problem in two: first, we describe the value that we want to generate with a `Random.Generator a`, then we generate a value. + +The first way to generate a value is to create a `Random.Seed` via `initialSeed : Int -> Seed` and then use `step : Generator a -> Seed -> ( a, Seed )`, which returns a value and a new seed. +Note that both of these functions are pure, so calling them twice with the same arguments will produce the same values. + +The second way to generate a value is by using `generate : (a -> msg) -> Generator a -> Cmd msg`, but that can only be done inside an Elm application. +In that case, the Elm runtime may use `step` as well as outside, non-pure resources to generate seeds. + +For the remaining of this module, we will focus on generators. +Let us pretend, for the sake of showing examples, that we have defined with `initialSeed` and `step` a function `generate : Int -> Generator a -> List a` that generates a number of values from a generator. + +The `Random` module provides two basic generators for generating integers and floats within a specific range: + +```elm +generate 5 (Random.int -5 5) + --> [0, 3, -5, 5, 0] + +generate 3 (Random.float 0 5) + --> [1.61803, 3.14159, 2.71828] +``` + +Those values can be combined into pairs or lists of values: + +```elm +generate 2 (Random.list 3 (Random.int 0 3)) + --> [[0, 3, 3], [1, 3, 2]] + +generate 2 (Random.pair (Random.int 0 3) (Random.float 10 10.3)) + --> [(0, 10.23412), (2, 10.17094)] +``` + +The (`elm-community/random-extra`)[random-extra] package provides a lot more generators for various data structures: strings, dates, dictionaries, arrays, sets, etc. + +We can create generators that will only return a single value using `Random.constant`: + +```elm +generate 4 (Random.constant "hello") + --> ["hello", "hello", "hello", "hello"] +``` + +We can randomly pick from given elements with equal probability using `Random.uniform`: + +```elm +generate 5 (Random.uniform Red [Green, Blue]) + --> [Red, Blue, Blue, Green, Red] +``` + +`Random.uniform` takes two arguments to guarantee that there is at least one value to pick from. + +We can also tweak the probabilities using `Random.weighted`: + +```elm +generate 5 (Random.weighted (Red, 80) [(Green, 15), (Blue, 5)]) + --> [Red, Red, Green, Red, Red] +``` + +The values do not need to add up to 100, they will get renormalized anyway. + +We can reach the inside of a generator with `Random.map`: + +```elm +generate 3 (Random.int 1 6 |> Random.map (\n -> n * 10)) + --> [30, 60, 10] +``` + +We can also use `Random.map2` all the way to `Random.map5` to combine more generators: + +```elm +position = + Random.map3 + (\x y z -> Position x y z) + (Random.float -100 100) + (Random.float -100 100) + (Random.float -100 100) + +generate 1 position + --> [Position 33.788 -98.321 10.0845] +``` + +For more complex uses, we have `Random.andThen : (a -> Generator b) -> Generator a -> Generator b` that can use the value generated by one generator to create another: + +```elm +bool = Random.uniform True [False] + +failHalfOfTheTime : Generator a -> Generator (Maybe a) +failHalfOfTheTime generator = + bool + |> Random.andThen + (\boolResult -> + if boolResult then + Random.map Just generator + + else + Random.constant Nothing + ) + +generate 6 (Random.int 0 1 |> failHalfOfTheTime) + --> [Nothing, Just 1, Just 0, Nothing, Just 1, Nothing] +``` + +It is sometimes useful to define a generator self-recursively. +In those cases, you might need to use `Random.lazy` to keep the compiler from unrolling the generator infinitely. + +```elm +type Peano + = Zero + | Next Peano + + +peano : Generator Peano +peano = + Random.uniform (Random.constant Zero) + [ Random.lazy (\_ -> peano) |> Random.map Next + ] + |> Random.andThen identity + +generate 3 peano + --> [Next(Next(Zero)), Zero, Next(Zero)] +``` + +[xkcd-random]: https://xkcd.com/221/ +[random-extra]: https://package.elm-lang.org/packages/elm-community/random-extra/latest/ diff --git a/concepts/random/introduction.md b/concepts/random/introduction.md new file mode 100644 index 00000000..8b6b94ff --- /dev/null +++ b/concepts/random/introduction.md @@ -0,0 +1,125 @@ +# Introduction + +In a pure functional language like Elm, a function called with the same arguments must always return the same value. +Therefore a function with the type signature `rand : Int` can only be implemented as `rand = 4`, which does not bode well for generating random integers. + +So how do we generate random values in Elm? +We split the problem in two: first, we describe the value that we want to generate with a `Random.Generator a`, then we generate a value. + +The first way to generate a value is to create a `Random.Seed` via `initialSeed : Int -> Seed` and then use `step : Generator a -> Seed -> ( a, Seed )`, which returns a value and a new seed. +Note that both of these functions are pure, so calling them twice with the same arguments will produce the same values. + +The second way to generate a value is by using `generate : (a -> msg) -> Generator a -> Cmd msg`, but that can only be done inside an Elm application. +In that case, the Elm runtime may use `step` as well as outside, non-pure resources to generate seeds. + +For the remaining of this module, we will focus on generators. +Let us pretend, for the sake of showing examples, that we have defined with `initialSeed` and `step` a function `generate : Int -> Generator a -> List a` that generates a number of values from a generator. + +The `Random` module provides two basic generators for generating integers and floats within a specific range: + +```elm +generate 5 (Random.int -5 5) + --> [0, 3, -5, 5, 0] + +generate 3 (Random.float 0 5) + --> [1.61803, 3.14159, 2.71828] +``` + +Those values can be combined into pairs or lists of values: + +```elm +generate 2 (Random.list 3 (Random.int 0 3)) + --> [[0, 3, 3], [1, 3, 2]] + +generate 2 (Random.pair (Random.int 0 3) (Random.float 10 10.3)) + --> [(0, 10.23412), (2, 10.17094)] +``` + +The `elm-community/random-extra` package provides a lot more generators for various data structures: strings, dates, dictionaries, arrays, sets, etc. + +We can create generators that will only return a single value using `Random.constant`: + +```elm +generate 4 (Random.constant "hello") + --> ["hello", "hello", "hello", "hello"] +``` + +We can randomly pick from given elements with equal probability using `Random.uniform`: + +```elm +generate 5 (Random.uniform Red [Green, Blue]) + --> [Red, Blue, Blue, Green, Red] +``` + +`Random.uniform` takes two arguments to guarantee that there is at least one value to pick from. + +We can also tweak the probabilities using `Random.weighted`: + +```elm +generate 5 (Random.weighted (Red, 80) [(Green, 15), (Blue, 5)]) + --> [Red, Red, Green, Red, Red] +``` + +The values do not need to add up to 100, they will get renormalized anyway. + +We can reach the inside of a generator with `Random.map`: + +```elm +generate 3 (Random.int 1 6 |> Random.map (\n -> n * 10)) + --> [30, 60, 10] +``` + +We can also use `Random.map2` all the way to `Random.map5` to combine more generators: + +```elm +position = + Random.map3 + (\x y z -> Position x y z) + (Random.float -100 100) + (Random.float -100 100) + (Random.float -100 100) + +generate 1 position + --> [Position 33.788 -98.321 10.0845] +``` + +For more complex uses, we have `Random.andThen : (a -> Generator b) -> Generator a -> Generator b` that can use the value generated by one generator to create another: + +```elm +bool = Random.uniform True [False] + +failHalfOfTheTime : Generator a -> Generator (Maybe a) +failHalfOfTheTime generator = + bool + |> Random.andThen + (\boolResult -> + if boolResult then + Random.map Just generator + + else + Random.constant Nothing + ) + +generate 6 (Random.int 0 1 |> failHalfOfTheTime) + --> [Nothing, Just 1, Just 0, Nothing, Just 1, Nothing] +``` + +It is sometimes useful to define a generator self-recursively. +In those cases, you might need to use `Random.lazy` to keep the compiler from unrolling the generator infinitely. + +```elm +type Peano + = Zero + | Next Peano + + +peano : Generator Peano +peano = + Random.uniform (Random.constant Zero) + [ Random.lazy (\_ -> peano) |> Random.map Next + ] + |> Random.andThen identity + +generate 3 peano + --> [Next(Next(Zero)), Zero, Next(Zero)] +``` diff --git a/concepts/random/links.json b/concepts/random/links.json new file mode 100644 index 00000000..96930a56 --- /dev/null +++ b/concepts/random/links.json @@ -0,0 +1,18 @@ +[ + { + "url": "https://package.elm-lang.org/packages/elm/random/latest/", + "description": "Random module documentation" + }, + { + "url": "https://guide.elm-lang.org/effects/random", + "description": "Elm guide on the Random module" + }, + { + "url": "https://package.elm-lang.org/packages/elm-community/random-extra/latest/", + "description": "random-extra package documentation" + }, + { + "url": "https://elmprogramming.com/commands.html", + "description": "Beginning Elm: Commands" + } +] From 8b3da1cf5684917fb11f75bed8085d0a920a66d2 Mon Sep 17 00:00:00 2001 From: Jie Date: Sun, 15 Oct 2023 20:28:23 +0800 Subject: [PATCH 03/18] Update exercises/concept/maze-maker/.docs/instructions.md Co-authored-by: Pete --- exercises/concept/maze-maker/.docs/instructions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/concept/maze-maker/.docs/instructions.md b/exercises/concept/maze-maker/.docs/instructions.md index fac4e919..f26909ee 100644 --- a/exercises/concept/maze-maker/.docs/instructions.md +++ b/exercises/concept/maze-maker/.docs/instructions.md @@ -10,7 +10,7 @@ You decide to approach your next project with a random generation approach. Any good maze is mostly dead ends. -Define the `deadend` generator so that it always generate the `DeadEnd` variant. +Define the `deadend` generator so that it always generates the `DeadEnd` variant. ## 2. You can't catch flies with vinegar From 68727c39c56e53433c84e3f0310d5b3d84e98dda Mon Sep 17 00:00:00 2001 From: Jie Date: Sun, 15 Oct 2023 20:29:12 +0800 Subject: [PATCH 04/18] Update exercises/concept/maze-maker/.docs/hints.md Co-authored-by: Pete --- exercises/concept/maze-maker/.docs/hints.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/concept/maze-maker/.docs/hints.md b/exercises/concept/maze-maker/.docs/hints.md index 23bbb8a1..2e4feeed 100644 --- a/exercises/concept/maze-maker/.docs/hints.md +++ b/exercises/concept/maze-maker/.docs/hints.md @@ -25,7 +25,7 @@ - Use the [`Random.weighted`][random-weighted] function for generating an uneven distribution of outcomes - Use the [`Random.lazy`][random-lazy] function for using a generator recursively - Read more about defining recursive data structures [here][bad-recursion] -- If you end up defining a nested `Generator (Generator Maze)` generator, you can use the [`Random.andThen`][random-andThen] function to flatten it +- If you end up defining a nested `Generator (Generator Maze)` generator, you can use [`Random.andThen`][random-andThen] together with the `identity` function to flatten it ## 5. We have to go deeper From 5d8ffd2339c48a190e612c318f1fff552350a3ab Mon Sep 17 00:00:00 2001 From: Jie Date: Tue, 17 Oct 2023 20:55:09 +0800 Subject: [PATCH 05/18] Update concepts/random/about.md Co-authored-by: Pete --- concepts/random/about.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/concepts/random/about.md b/concepts/random/about.md index ffd0b6d1..f4f823f4 100644 --- a/concepts/random/about.md +++ b/concepts/random/about.md @@ -15,7 +15,7 @@ In that case, the Elm runtime may use `step` as well as outside, non-pure resour For the remaining of this module, we will focus on generators. Let us pretend, for the sake of showing examples, that we have defined with `initialSeed` and `step` a function `generate : Int -> Generator a -> List a` that generates a number of values from a generator. -The `Random` module provides two basic generators for generating integers and floats within a specific range: +The `Random` module provides two basic generators for creating integers and floats within a specific range: ```elm generate 5 (Random.int -5 5) From 233bd6fbdce9c01ae5d7fea42cc12961bcc97e71 Mon Sep 17 00:00:00 2001 From: Jeremie Gillet Date: Tue, 17 Oct 2023 21:54:51 +0900 Subject: [PATCH 06/18] Expand on seed in about.md --- concepts/random/about.md | 50 +++++++++++++++++++++++++-------- concepts/random/introduction.md | 13 +++++---- 2 files changed, 46 insertions(+), 17 deletions(-) diff --git a/concepts/random/about.md b/concepts/random/about.md index f4f823f4..a65774a2 100644 --- a/concepts/random/about.md +++ b/concepts/random/about.md @@ -6,14 +6,42 @@ Therefore a function with the type signature `rand : Int` can only be implemente So how do we generate random values in Elm? We split the problem in two: first, we describe the value that we want to generate with a `Random.Generator a`, then we generate a value. -The first way to generate a value is to create a `Random.Seed` via `initialSeed : Int -> Seed` and then use `step : Generator a -> Seed -> ( a, Seed )`, which returns a value and a new seed. -Note that both of these functions are pure, so calling them twice with the same arguments will produce the same values. +The first way to generate a value is to create a `Random.Seed` via `initialSeed : Int -> Seed`. +A `Seed` is an opaque type that contains an integer and knows how to transform that integer in a way that will appear random (called pseudorandom). +Once you have a `Seed`, you can use it to generate a value with a `Generator a`. +A `Generator a` is an opaque type that knows how to use the integer inside of a seed to create a pseudorandom value of type `a` as well as a new seed with an updated integer. -The second way to generate a value is by using `generate : (a -> msg) -> Generator a -> Cmd msg`, but that can only be done inside an Elm application. -In that case, the Elm runtime may use `step` as well as outside, non-pure resources to generate seeds. +To generate a value, we can use `step : Generator a -> Seed -> ( a, Seed )`, which returns a value and a new seed that we can use to generate further values. -For the remaining of this module, we will focus on generators. -Let us pretend, for the sake of showing examples, that we have defined with `initialSeed` and `step` a function `generate : Int -> Generator a -> List a` that generates a number of values from a generator. +Let's use these functions to extract `n` random values out of a generator: + +```elm +generate : Int -> Generator a -> List a +generate n generator = generateValuesFromSeed n generator (Random.initialSeed 42) + +generateValuesFromSeed : Int -> Generator a -> Seed -> List a +generateValuesFromSeed n generator seed = + if n <= 0 then + [] + else + let ( value, nextSeed ) = Random.step generator seed + in value :: generateValuesFromSeed (n - 1) generator nextSeed +``` + +Note that all of these functions are pure, so calling them twice with the same arguments will produce the same values. + +```elm +generate 10 (Random.int 1 6) + --> [4, 5, 3, 3, 5, 1, 2, 4, 6, 6] + +generate 10 (Random.int 1 6) + --> [4, 5, 3, 3, 5, 1, 2, 4, 6, 6] +``` + +The second way to generate a value is by using `Random.generate : (a -> msg) -> Generator a -> Cmd msg`, but that can only be done inside an Elm application. +In that case, the Elm runtime may use `step` as well as outside, non-pure resources to generate seeds, and calling the same function twice will give different results. + +For now on, we will focus on generators. The `Random` module provides two basic generators for creating integers and floats within a specific range: @@ -73,11 +101,11 @@ We can also use `Random.map2` all the way to `Random.map5` to combine more gener ```elm position = - Random.map3 - (\x y z -> Position x y z) - (Random.float -100 100) - (Random.float -100 100) - (Random.float -100 100) + Random.map3 + (\x y z -> Position x y z) + (Random.float -100 100) + (Random.float -100 100) + (Random.float -100 100) generate 1 position --> [Position 33.788 -98.321 10.0845] diff --git a/concepts/random/introduction.md b/concepts/random/introduction.md index 8b6b94ff..dbf6ffea 100644 --- a/concepts/random/introduction.md +++ b/concepts/random/introduction.md @@ -12,7 +12,8 @@ Note that both of these functions are pure, so calling them twice with the same The second way to generate a value is by using `generate : (a -> msg) -> Generator a -> Cmd msg`, but that can only be done inside an Elm application. In that case, the Elm runtime may use `step` as well as outside, non-pure resources to generate seeds. -For the remaining of this module, we will focus on generators. +For now on, we will focus on generators. + Let us pretend, for the sake of showing examples, that we have defined with `initialSeed` and `step` a function `generate : Int -> Generator a -> List a` that generates a number of values from a generator. The `Random` module provides two basic generators for generating integers and floats within a specific range: @@ -73,11 +74,11 @@ We can also use `Random.map2` all the way to `Random.map5` to combine more gener ```elm position = - Random.map3 - (\x y z -> Position x y z) - (Random.float -100 100) - (Random.float -100 100) - (Random.float -100 100) + Random.map3 + (\x y z -> Position x y z) + (Random.float -100 100) + (Random.float -100 100) + (Random.float -100 100) generate 1 position --> [Position 33.788 -98.321 10.0845] From 38cd786b12e3fb60825fae35fdbe47713c1a78d4 Mon Sep 17 00:00:00 2001 From: Jeremie Gillet Date: Thu, 19 Oct 2023 21:12:53 +0900 Subject: [PATCH 07/18] Add random concept to dnd-character --- config.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/config.json b/config.json index c86d79f1..c236c93c 100644 --- a/config.json +++ b/config.json @@ -1231,8 +1231,11 @@ "slug": "dnd-character", "name": "D&D Character", "uuid": "c22b805f-36db-407c-92d1-39ba0e4fa041", - "practices": [], + "practices": [ + "random" + ], "prerequisites": [ + "random", "lists", "records" ], From 7024cda3056db0c96fd153a6c91a370d3d1addaa Mon Sep 17 00:00:00 2001 From: Jie Date: Fri, 20 Oct 2023 19:34:49 +0800 Subject: [PATCH 08/18] Apply suggestions from code review Co-authored-by: Cedd Burge --- concepts/random/about.md | 2 +- exercises/concept/maze-maker/.docs/hints.md | 2 +- exercises/concept/maze-maker/.meta/design.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/concepts/random/about.md b/concepts/random/about.md index a65774a2..8561d338 100644 --- a/concepts/random/about.md +++ b/concepts/random/about.md @@ -41,7 +41,7 @@ generate 10 (Random.int 1 6) The second way to generate a value is by using `Random.generate : (a -> msg) -> Generator a -> Cmd msg`, but that can only be done inside an Elm application. In that case, the Elm runtime may use `step` as well as outside, non-pure resources to generate seeds, and calling the same function twice will give different results. -For now on, we will focus on generators. +From now on, we will focus on generators. The `Random` module provides two basic generators for creating integers and floats within a specific range: diff --git a/exercises/concept/maze-maker/.docs/hints.md b/exercises/concept/maze-maker/.docs/hints.md index 2e4feeed..75759c9b 100644 --- a/exercises/concept/maze-maker/.docs/hints.md +++ b/exercises/concept/maze-maker/.docs/hints.md @@ -29,7 +29,7 @@ ## 5. We have to go deeper -- Use the [`Random.uniform`][random-uniform] function for generating the dead ends or rooms in on depth `0` +- Use the [`Random.uniform`][random-uniform] function for generating the dead ends or rooms at depth `0` - If you end up defining a nested `Generator (Generator Maze)` generator, you can use the [`Random.andThen`][random-andThen] function to flatten it - Use the [`Random.lazy`][random-lazy] function for using a generator recursively - Read more about defining recursive data structures [here][bad-recursion] diff --git a/exercises/concept/maze-maker/.meta/design.md b/exercises/concept/maze-maker/.meta/design.md index 539d65a7..9db6de9e 100644 --- a/exercises/concept/maze-maker/.meta/design.md +++ b/exercises/concept/maze-maker/.meta/design.md @@ -21,7 +21,7 @@ Students will be able to - Using `generate` (we can only mention because it returns a `Cmd msg`) - Generate values: `Seed`, `step`, `initialSeed` (only mentioned, not used) - `independentSeed` -- `maxInt`, `minTint` +- `maxInt`, `minInt` ## Concepts From d240576e2e24a5e7373ebea9dc7003af9dfd2f5f Mon Sep 17 00:00:00 2001 From: Jeremie Gillet Date: Thu, 19 Oct 2023 21:56:52 +0900 Subject: [PATCH 09/18] formatting --- config.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/config.json b/config.json index c236c93c..a50ad869 100644 --- a/config.json +++ b/config.json @@ -477,9 +477,9 @@ "lists", "custom-types" ] - }, - { - "slug": "squeaky-clean", + }, + { + "slug": "squeaky-clean", "name": "Squeaky Clean", "uuid": "ca425c58-26ec-48d3-8e2d-a9d5fb8cb74e", "concepts": [ From d5c96d177d93ece89254e1c9fdf5bce67800c5c0 Mon Sep 17 00:00:00 2001 From: Jeremie Gillet Date: Fri, 20 Oct 2023 09:23:41 +0900 Subject: [PATCH 10/18] Add contributors to dnd-character --- exercises/practice/dnd-character/.meta/config.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/exercises/practice/dnd-character/.meta/config.json b/exercises/practice/dnd-character/.meta/config.json index 8efc597f..0f3ed6fd 100644 --- a/exercises/practice/dnd-character/.meta/config.json +++ b/exercises/practice/dnd-character/.meta/config.json @@ -2,6 +2,10 @@ "authors": [ "jiegillet" ], + "contributors": [ + "ceddlyburge", + "mpizenberg" + ], "files": { "solution": [ "src/DndCharacter.elm" From 05f102cdb7a4aa96926fb69dca3de3a3b9ed9319 Mon Sep 17 00:00:00 2001 From: Jeremie Gillet Date: Fri, 20 Oct 2023 20:46:30 +0900 Subject: [PATCH 11/18] Grammar fixes --- concepts/random/about.md | 4 ++-- concepts/random/introduction.md | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/concepts/random/about.md b/concepts/random/about.md index 8561d338..4ef3325b 100644 --- a/concepts/random/about.md +++ b/concepts/random/about.md @@ -53,7 +53,7 @@ generate 3 (Random.float 0 5) --> [1.61803, 3.14159, 2.71828] ``` -Those values can be combined into pairs or lists of values: +Those values can be combined into tuples, or into lists of values: ```elm generate 2 (Random.list 3 (Random.int 0 3)) @@ -79,7 +79,7 @@ generate 5 (Random.uniform Red [Green, Blue]) --> [Red, Blue, Blue, Green, Red] ``` -`Random.uniform` takes two arguments to guarantee that there is at least one value to pick from. +`Random.uniform` takes two arguments (`Red` and `[Green, Blue]`) to guarantee that there is at least one value to pick from, since a single list could be empty. We can also tweak the probabilities using `Random.weighted`: diff --git a/concepts/random/introduction.md b/concepts/random/introduction.md index dbf6ffea..95788474 100644 --- a/concepts/random/introduction.md +++ b/concepts/random/introduction.md @@ -12,7 +12,7 @@ Note that both of these functions are pure, so calling them twice with the same The second way to generate a value is by using `generate : (a -> msg) -> Generator a -> Cmd msg`, but that can only be done inside an Elm application. In that case, the Elm runtime may use `step` as well as outside, non-pure resources to generate seeds. -For now on, we will focus on generators. +From now on, we will focus on generators. Let us pretend, for the sake of showing examples, that we have defined with `initialSeed` and `step` a function `generate : Int -> Generator a -> List a` that generates a number of values from a generator. @@ -26,7 +26,7 @@ generate 3 (Random.float 0 5) --> [1.61803, 3.14159, 2.71828] ``` -Those values can be combined into pairs or lists of values: +Those values can be combined into tuples, or into lists of values: ```elm generate 2 (Random.list 3 (Random.int 0 3)) @@ -52,7 +52,7 @@ generate 5 (Random.uniform Red [Green, Blue]) --> [Red, Blue, Blue, Green, Red] ``` -`Random.uniform` takes two arguments to guarantee that there is at least one value to pick from. +`Random.uniform` takes two arguments (`Red` and `[Green, Blue]`) to guarantee that there is at least one value to pick from, since a single list could be empty. We can also tweak the probabilities using `Random.weighted`: From 5fbe72fbb2814acf9f1f12a51a6107b86d00fad3 Mon Sep 17 00:00:00 2001 From: Jeremie Gillet Date: Fri, 20 Oct 2023 20:47:27 +0900 Subject: [PATCH 12/18] More detailed hint --- exercises/concept/maze-maker/.docs/hints.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/concept/maze-maker/.docs/hints.md b/exercises/concept/maze-maker/.docs/hints.md index 75759c9b..d21c34d5 100644 --- a/exercises/concept/maze-maker/.docs/hints.md +++ b/exercises/concept/maze-maker/.docs/hints.md @@ -30,7 +30,7 @@ ## 5. We have to go deeper - Use the [`Random.uniform`][random-uniform] function for generating the dead ends or rooms at depth `0` -- If you end up defining a nested `Generator (Generator Maze)` generator, you can use the [`Random.andThen`][random-andThen] function to flatten it +- If you end up defining a nested `Generator (Generator Maze)` generator, you can use [`Random.andThen`][random-andThen] together with the `identity` function to flatten it - Use the [`Random.lazy`][random-lazy] function for using a generator recursively - Read more about defining recursive data structures [here][bad-recursion] From cbda11b423cde6d3cdd7438dc37e618acc885c37 Mon Sep 17 00:00:00 2001 From: Jeremie Gillet Date: Fri, 20 Oct 2023 20:50:53 +0900 Subject: [PATCH 13/18] More peano --- concepts/random/about.md | 4 ++-- concepts/random/introduction.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/concepts/random/about.md b/concepts/random/about.md index 4ef3325b..5f16de21 100644 --- a/concepts/random/about.md +++ b/concepts/random/about.md @@ -148,8 +148,8 @@ peano = ] |> Random.andThen identity -generate 3 peano - --> [Next(Next(Zero)), Zero, Next(Zero)] +generate 12 peano + --> [Zero, Next(Next(Zero)), Zero, Next(Zero), Zero, Zero, Next(Zero), Zero, Zero, Next(Zero), Next(Next(Next(Zero)))] ``` [xkcd-random]: https://xkcd.com/221/ diff --git a/concepts/random/introduction.md b/concepts/random/introduction.md index 95788474..775493cf 100644 --- a/concepts/random/introduction.md +++ b/concepts/random/introduction.md @@ -121,6 +121,6 @@ peano = ] |> Random.andThen identity -generate 3 peano - --> [Next(Next(Zero)), Zero, Next(Zero)] +generate 12 peano + --> [Zero, Next(Next(Zero)), Zero, Next(Zero), Zero, Zero, Next(Zero), Zero, Zero, Next(Zero), Next(Next(Next(Zero)))] ``` From 5bc84399cf2ee76672ee713393c74ddc7e6f8e2d Mon Sep 17 00:00:00 2001 From: Jeremie Gillet Date: Sat, 21 Oct 2023 10:33:40 +0900 Subject: [PATCH 14/18] format config file --- config.json | 326 ++++++++++++++++++++++++++-------------------------- 1 file changed, 162 insertions(+), 164 deletions(-) diff --git a/config.json b/config.json index a50ad869..c4807627 100644 --- a/config.json +++ b/config.json @@ -2,6 +2,12 @@ "language": "Elm", "slug": "elm", "active": true, + "status": { + "concept_exercises": true, + "test_runner": true, + "representer": true, + "analyzer": true + }, "blurb": "Elm is a friendly functional language for the Web", "version": 3, "online_editor": { @@ -12,12 +18,6 @@ "test_runner": { "average_run_time": 2 }, - "status": { - "concept_exercises": true, - "test_runner": true, - "representer": true, - "analyzer": true - }, "files": { "solution": [ "src/%{pascal_slug}.elm" @@ -32,162 +32,7 @@ ".meta/Exemplar.elm" ] }, - "tags": [ - "paradigm/declarative", - "paradigm/functional", - "typing/static", - "typing/strong", - "execution_mode/compiled", - "platform/web", - "used_for/frontends", - "used_for/web_development" - ], - "key_features": [ - { - "title": "For the Web", - "content": "Elm is for making Web applications.", - "icon": "web" - }, - { - "title": "Delightful", - "content": "Elm aims at the most enjoyable developer experience possible.", - "icon": "fun" - }, - { - "title": "Excellent performance", - "content": "Elm has tiny compilation times, small assets and a fast runtime.", - "icon": "fast" - }, - { - "title": "No runtime exception", - "content": "Thanks to strict typing, Elm catches potential errors at compilation.", - "icon": "safe" - }, - { - "title": "JavaScript interop", - "content": "Elm has a dedicated interop mechanism with JavaScript called ports.", - "icon": "interop" - }, - { - "title": "Functional but simple", - "content": "Elm is a simple pure functional language, a good gateway for more advanced functional languages.", - "icon": "functional" - } - ], - "concepts": [ - { - "uuid": "3e6e4a21-cc66-4c48-857b-90e1ca5298f9", - "slug": "basics-1", - "name": "Basics 1" - }, - { - "uuid": "e850acd6-63f3-427e-a883-222e0bd89a59", - "slug": "basics-2", - "name": "Basics 2" - }, - { - "uuid": "2f7f1b64-5ee5-4600-a81d-87a2c79815a5", - "slug": "booleans", - "name": "Booleans" - }, - { - "uuid": "33944880-b04c-4c36-9d30-e8859a39d3fa", - "slug": "lists", - "name": "Lists" - }, - { - "uuid": "100cbb44-6ecd-4717-86bd-6b51b6f890a2", - "slug": "records", - "name": "Records" - }, - { - "uuid": "7e55adb0-c813-4d31-a2e2-4381b38f0d87", - "slug": "custom-types", - "name": "Custom Types" - }, - { - "uuid": "036f1e5a-dcdc-4625-abce-5aba7e60e0e7", - "slug": "tuples", - "name": "Tuples" - }, - { - "uuid": "4a948be3-4c5e-407f-b29d-a7e7f00fee67", - "slug": "maybe", - "name": "Maybe" - }, - { - "uuid": "c3023ebe-bdd1-4ab2-827d-c08aaef90b26", - "slug": "result", - "name": "Result" - }, - { - "uuid": "e2d53c4c-c3f1-481d-afcb-1e17c9950311", - "slug": "let", - "name": "Let" - }, - { - "uuid": "1ad37090-da68-40c3-a099-a5043ed7eb47", - "slug": "pattern-matching", - "name": "Pattern Matching" - }, - { - "uuid": "b8ac1007-02dc-4dc4-bdc2-a9f3d7886134", - "slug": "dict", - "name": "Dict" - }, - { - "slug": "parsing", - "name": "Parsing", - "uuid": "5842f21e-9fcf-4cf4-a710-19674ecb19c1" - }, - { - "slug": "partial-application-composition", - "name": "Partial application and function composition", - "uuid": "3fde0ff5-d603-4f40-a7e3-a4ea2707af90" - }, - { - "slug": "comparison", - "name": "Comparison", - "uuid": "a5339d63-c63a-491b-996b-6666a2bdea99" - }, - { - "slug": "set", - "name": "Set", - "uuid": "cfa44843-b783-4366-a198-f0e99e632143" - }, - { - "slug": "generics", - "name": "Generics", - "uuid": "a967b004-e754-4664-8103-6a98be4d00e5" - }, - { - "slug": "opaque-types", - "name": "Opaque Types", - "uuid": "35b389dd-16fe-405f-a3ce-c08673ee49ae" - }, - { - "slug": "phantom-types", - "name": "Phantom Types", - "uuid": "84500f05-f03c-4de4-ab9e-dc2ba0bd1a65" - }, - { - "slug": "array", - "name": "Arrays", - "uuid": "de4717c0-56e5-482f-bfc7-dc8cd1b6f2d0" - }, - { - "uuid": "563a82fc-bb43-4866-a3d2-fd35eee5efa5", - "slug": "strings", - "name": "Strings" - }, - { - "slug": "random", - "name": "Random", - "uuid": "357ffb6e-5a73-49b6-8b69-98ecd3c74c33" - } - ], "exercises": { - "foregone": [], "concept": [ { "slug": "lucians-luscious-lasagna", @@ -814,8 +659,7 @@ "comparison", "booleans" ], - "difficulty": 3, - "topics": [] + "difficulty": 3 }, { "slug": "pythagorean-triplet", @@ -1242,5 +1086,159 @@ "difficulty": 5 } ] - } + }, + "concepts": [ + { + "uuid": "3e6e4a21-cc66-4c48-857b-90e1ca5298f9", + "slug": "basics-1", + "name": "Basics 1" + }, + { + "uuid": "e850acd6-63f3-427e-a883-222e0bd89a59", + "slug": "basics-2", + "name": "Basics 2" + }, + { + "uuid": "2f7f1b64-5ee5-4600-a81d-87a2c79815a5", + "slug": "booleans", + "name": "Booleans" + }, + { + "uuid": "33944880-b04c-4c36-9d30-e8859a39d3fa", + "slug": "lists", + "name": "Lists" + }, + { + "uuid": "100cbb44-6ecd-4717-86bd-6b51b6f890a2", + "slug": "records", + "name": "Records" + }, + { + "uuid": "7e55adb0-c813-4d31-a2e2-4381b38f0d87", + "slug": "custom-types", + "name": "Custom Types" + }, + { + "uuid": "036f1e5a-dcdc-4625-abce-5aba7e60e0e7", + "slug": "tuples", + "name": "Tuples" + }, + { + "uuid": "4a948be3-4c5e-407f-b29d-a7e7f00fee67", + "slug": "maybe", + "name": "Maybe" + }, + { + "uuid": "c3023ebe-bdd1-4ab2-827d-c08aaef90b26", + "slug": "result", + "name": "Result" + }, + { + "uuid": "e2d53c4c-c3f1-481d-afcb-1e17c9950311", + "slug": "let", + "name": "Let" + }, + { + "uuid": "1ad37090-da68-40c3-a099-a5043ed7eb47", + "slug": "pattern-matching", + "name": "Pattern Matching" + }, + { + "uuid": "b8ac1007-02dc-4dc4-bdc2-a9f3d7886134", + "slug": "dict", + "name": "Dict" + }, + { + "uuid": "5842f21e-9fcf-4cf4-a710-19674ecb19c1", + "slug": "parsing", + "name": "Parsing" + }, + { + "uuid": "3fde0ff5-d603-4f40-a7e3-a4ea2707af90", + "slug": "partial-application-composition", + "name": "Partial application and function composition" + }, + { + "uuid": "a5339d63-c63a-491b-996b-6666a2bdea99", + "slug": "comparison", + "name": "Comparison" + }, + { + "uuid": "cfa44843-b783-4366-a198-f0e99e632143", + "slug": "set", + "name": "Set" + }, + { + "uuid": "a967b004-e754-4664-8103-6a98be4d00e5", + "slug": "generics", + "name": "Generics" + }, + { + "uuid": "35b389dd-16fe-405f-a3ce-c08673ee49ae", + "slug": "opaque-types", + "name": "Opaque Types" + }, + { + "uuid": "84500f05-f03c-4de4-ab9e-dc2ba0bd1a65", + "slug": "phantom-types", + "name": "Phantom Types" + }, + { + "uuid": "de4717c0-56e5-482f-bfc7-dc8cd1b6f2d0", + "slug": "array", + "name": "Arrays" + }, + { + "uuid": "563a82fc-bb43-4866-a3d2-fd35eee5efa5", + "slug": "strings", + "name": "Strings" + }, + { + "uuid": "357ffb6e-5a73-49b6-8b69-98ecd3c74c33", + "slug": "random", + "name": "Random" + } + ], + "key_features": [ + { + "title": "For the Web", + "content": "Elm is for making Web applications.", + "icon": "web" + }, + { + "title": "Delightful", + "content": "Elm aims at the most enjoyable developer experience possible.", + "icon": "fun" + }, + { + "title": "Excellent performance", + "content": "Elm has tiny compilation times, small assets and a fast runtime.", + "icon": "fast" + }, + { + "title": "No runtime exception", + "content": "Thanks to strict typing, Elm catches potential errors at compilation.", + "icon": "safe" + }, + { + "title": "JavaScript interop", + "content": "Elm has a dedicated interop mechanism with JavaScript called ports.", + "icon": "interop" + }, + { + "title": "Functional but simple", + "content": "Elm is a simple pure functional language, a good gateway for more advanced functional languages.", + "icon": "functional" + } + ], + "tags": [ + "execution_mode/compiled", + "paradigm/declarative", + "paradigm/functional", + "platform/web", + "typing/static", + "typing/strong", + "used_for/frontends", + "used_for/web_development" + ] } From 2b0732de91c18562b7d9205a3f54202ff71ae382 Mon Sep 17 00:00:00 2001 From: Jeremie Gillet Date: Sat, 21 Oct 2023 11:14:19 +0900 Subject: [PATCH 15/18] Expand on the peano example --- concepts/random/about.md | 22 +++++++++++++++++++++- concepts/random/introduction.md | 22 +++++++++++++++++++++- 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/concepts/random/about.md b/concepts/random/about.md index 5f16de21..356ac186 100644 --- a/concepts/random/about.md +++ b/concepts/random/about.md @@ -144,7 +144,7 @@ type Peano peano : Generator Peano peano = Random.uniform (Random.constant Zero) - [ Random.lazy (\_ -> peano) |> Random.map Next + [ Random.map Next (Random.lazy (\_ -> peano)) ] |> Random.andThen identity @@ -152,5 +152,25 @@ generate 12 peano --> [Zero, Next(Next(Zero)), Zero, Next(Zero), Zero, Zero, Next(Zero), Zero, Zero, Next(Zero), Next(Next(Next(Zero)))] ``` +This example is a little heavy, so let's break it down. + +`Peano` is a type that represents positive integers: `Zero` is 0, `Next(Zero)` is 1, `Next(Next(Zero))` is 2, etc. +We define `peano` to give us a random `Peano` number. + +First of all, note that `Random.lazy` doesn't add anything to the logic, it merely prevents the compiler from writing `peano` into `peano` indefinitely. + +We use `Random.uniform` to pick between zero (with 50% probability) and another `Peano` number plus one (with 50% probability). +However, unlike with the previous example, `Random.uniform` is not picking from values (like `Zero`) but instead from generators (like `Random.constant Zero`) since we want to use `peano` itself. +This means that `Random.uniform` will return a value of type `Generator (Generator Peano)`, which is not want we need. + +To "flatten" the generator, we pipe it into `Random.andThen : (a -> Generator b) -> Generator a -> Generator b`, where we want `b` to be `Peano`. +Since `Generator a` is `Generator (Generator Peano)`, `a` must be `Generator Peano`, and the function `(a -> Generator b)` must be of type `(Generator Peano -> Generator Peano)`. +We don't want to modify anything, so `identity` is the right choice. + +Finally, what kind of numbers will `peano` produce? +We know that it will produce 0 50% of the time, or another iteration of itself plus one 50% of the time. +That means the numbers will be 0 (50% of the time), 1 (25% of the time), 2 (12% of the time), 3 (6% of the time), etc. +`peano` can produce arbitrary large numbers, but with exponentially decreasing probability. + [xkcd-random]: https://xkcd.com/221/ [random-extra]: https://package.elm-lang.org/packages/elm-community/random-extra/latest/ diff --git a/concepts/random/introduction.md b/concepts/random/introduction.md index 775493cf..73e24a86 100644 --- a/concepts/random/introduction.md +++ b/concepts/random/introduction.md @@ -117,10 +117,30 @@ type Peano peano : Generator Peano peano = Random.uniform (Random.constant Zero) - [ Random.lazy (\_ -> peano) |> Random.map Next + [ Random.map Next (Random.lazy (\_ -> peano)) ] |> Random.andThen identity generate 12 peano --> [Zero, Next(Next(Zero)), Zero, Next(Zero), Zero, Zero, Next(Zero), Zero, Zero, Next(Zero), Next(Next(Next(Zero)))] ``` + +This example is a little heavy, so let's break it down. + +`Peano` is a type that represents positive integers: `Zero` is 0, `Next(Zero)` is 1, `Next(Next(Zero))` is 2, etc. +We define `peano` to give us a random `Peano` number. + +First of all, note that `Random.lazy` doesn't add anything to the logic, it merely prevents the compiler from writing `peano` into `peano` indefinitely. + +We use `Random.uniform` to pick between zero (with 50% probability) and another `Peano` number plus one (with 50% probability). +However, unlike with the previous example, `Random.uniform` is not picking from values (like `Zero`) but instead from generators (like `Random.constant Zero`) since we want to use `peano` itself. +This means that `Random.uniform` will return a value of type `Generator (Generator Peano)`, which is not want we need. + +To "flatten" the generator, we pipe it into `Random.andThen : (a -> Generator b) -> Generator a -> Generator b`, where we want `b` to be `Peano`. +Since `Generator a` is `Generator (Generator Peano)`, `a` must be `Generator Peano`, and the function `(a -> Generator b)` must be of type `(Generator Peano -> Generator Peano)`. +We don't want to modify anything, so `identity` is the right choice. + +Finally, what kind of numbers will `peano` produce? +We know that it will produce 0 50% of the time, or another iteration of itself plus one 50% of the time. +That means the numbers will be 0 (50% of the time), 1 (25% of the time), 2 (12% of the time), 3 (6% of the time), etc. +`peano` can produce arbitrary large numbers, but with exponentially decreasing probability. From d8774ce582bd7e1440454889bd63e491fff6684b Mon Sep 17 00:00:00 2001 From: Jeremie Gillet Date: Sat, 21 Oct 2023 12:19:02 +0900 Subject: [PATCH 16/18] wrap todos in lazy --- exercises/concept/maze-maker/src/MazeMaker.elm | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/exercises/concept/maze-maker/src/MazeMaker.elm b/exercises/concept/maze-maker/src/MazeMaker.elm index 1cc42a69..7c0a635c 100644 --- a/exercises/concept/maze-maker/src/MazeMaker.elm +++ b/exercises/concept/maze-maker/src/MazeMaker.elm @@ -17,29 +17,29 @@ type Treasure deadend : Generator Maze deadend = - Debug.todo "Please implement deadend" + Random.lazy (\_ -> Debug.todo "Please implement deadend") treasure : Generator Treasure treasure = - Debug.todo "Please implement treasure" + Random.lazy (\_ -> Debug.todo "Please implement treasure") room : Generator Maze room = - Debug.todo "Please implement room" + Random.lazy (\_ -> Debug.todo "Please implement room") branch : Generator Maze -> Generator Maze branch mazeGenerator = - Debug.todo "Please implement branch" + Random.lazy (\_ -> Debug.todo "Please implement branch") maze : Generator Maze maze = - Debug.todo "Please implement maze" + Random.lazy (\_ -> Debug.todo "Please implement maze") mazeOfDepth : Int -> Generator Maze mazeOfDepth depth = - Debug.todo "Please implement mazeOfDepth" + Random.lazy (\_ -> Debug.todo "Please implement mazeOfDepth") From d19fcd710d36e6b7b8f067f9161d453c67c55efe Mon Sep 17 00:00:00 2001 From: Jeremie Gillet Date: Sat, 21 Oct 2023 19:18:22 +0900 Subject: [PATCH 17/18] Add contributors --- concepts/random/.meta/config.json | 4 ++++ exercises/concept/maze-maker/.meta/config.json | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/concepts/random/.meta/config.json b/concepts/random/.meta/config.json index 1c27dc85..238db0ab 100644 --- a/concepts/random/.meta/config.json +++ b/concepts/random/.meta/config.json @@ -2,5 +2,9 @@ "blurb": "Learn how to generate random values in Elm", "authors": [ "jiegillet" + ], + "contributors": [ + "pwadsworth", + "ceddlyburge" ] } diff --git a/exercises/concept/maze-maker/.meta/config.json b/exercises/concept/maze-maker/.meta/config.json index 478d8ebd..c6cb6ac2 100644 --- a/exercises/concept/maze-maker/.meta/config.json +++ b/exercises/concept/maze-maker/.meta/config.json @@ -2,6 +2,10 @@ "authors": [ "jiegillet" ], + "contributors": [ + "pwadsworth", + "ceddlyburge" + ], "files": { "solution": [ "src/MazeMaker.elm" From ac599cd2fee38afdb15e853bad10311126db5ca9 Mon Sep 17 00:00:00 2001 From: Jeremie Gillet Date: Sat, 21 Oct 2023 19:20:24 +0900 Subject: [PATCH 18/18] configlet generate --- .../concept/maze-maker/.docs/introduction.md | 148 ++++++++++++++++++ .../squeaky-clean/.docs/introduction.md | 12 +- 2 files changed, 155 insertions(+), 5 deletions(-) create mode 100644 exercises/concept/maze-maker/.docs/introduction.md diff --git a/exercises/concept/maze-maker/.docs/introduction.md b/exercises/concept/maze-maker/.docs/introduction.md new file mode 100644 index 00000000..5d5b1cae --- /dev/null +++ b/exercises/concept/maze-maker/.docs/introduction.md @@ -0,0 +1,148 @@ +# Introduction + +## Random + +In a pure functional language like Elm, a function called with the same arguments must always return the same value. +Therefore a function with the type signature `rand : Int` can only be implemented as `rand = 4`, which does not bode well for generating random integers. + +So how do we generate random values in Elm? +We split the problem in two: first, we describe the value that we want to generate with a `Random.Generator a`, then we generate a value. + +The first way to generate a value is to create a `Random.Seed` via `initialSeed : Int -> Seed` and then use `step : Generator a -> Seed -> ( a, Seed )`, which returns a value and a new seed. +Note that both of these functions are pure, so calling them twice with the same arguments will produce the same values. + +The second way to generate a value is by using `generate : (a -> msg) -> Generator a -> Cmd msg`, but that can only be done inside an Elm application. +In that case, the Elm runtime may use `step` as well as outside, non-pure resources to generate seeds. + +From now on, we will focus on generators. + +Let us pretend, for the sake of showing examples, that we have defined with `initialSeed` and `step` a function `generate : Int -> Generator a -> List a` that generates a number of values from a generator. + +The `Random` module provides two basic generators for generating integers and floats within a specific range: + +```elm +generate 5 (Random.int -5 5) + --> [0, 3, -5, 5, 0] + +generate 3 (Random.float 0 5) + --> [1.61803, 3.14159, 2.71828] +``` + +Those values can be combined into tuples, or into lists of values: + +```elm +generate 2 (Random.list 3 (Random.int 0 3)) + --> [[0, 3, 3], [1, 3, 2]] + +generate 2 (Random.pair (Random.int 0 3) (Random.float 10 10.3)) + --> [(0, 10.23412), (2, 10.17094)] +``` + +The `elm-community/random-extra` package provides a lot more generators for various data structures: strings, dates, dictionaries, arrays, sets, etc. + +We can create generators that will only return a single value using `Random.constant`: + +```elm +generate 4 (Random.constant "hello") + --> ["hello", "hello", "hello", "hello"] +``` + +We can randomly pick from given elements with equal probability using `Random.uniform`: + +```elm +generate 5 (Random.uniform Red [Green, Blue]) + --> [Red, Blue, Blue, Green, Red] +``` + +`Random.uniform` takes two arguments (`Red` and `[Green, Blue]`) to guarantee that there is at least one value to pick from, since a single list could be empty. + +We can also tweak the probabilities using `Random.weighted`: + +```elm +generate 5 (Random.weighted (Red, 80) [(Green, 15), (Blue, 5)]) + --> [Red, Red, Green, Red, Red] +``` + +The values do not need to add up to 100, they will get renormalized anyway. + +We can reach the inside of a generator with `Random.map`: + +```elm +generate 3 (Random.int 1 6 |> Random.map (\n -> n * 10)) + --> [30, 60, 10] +``` + +We can also use `Random.map2` all the way to `Random.map5` to combine more generators: + +```elm +position = + Random.map3 + (\x y z -> Position x y z) + (Random.float -100 100) + (Random.float -100 100) + (Random.float -100 100) + +generate 1 position + --> [Position 33.788 -98.321 10.0845] +``` + +For more complex uses, we have `Random.andThen : (a -> Generator b) -> Generator a -> Generator b` that can use the value generated by one generator to create another: + +```elm +bool = Random.uniform True [False] + +failHalfOfTheTime : Generator a -> Generator (Maybe a) +failHalfOfTheTime generator = + bool + |> Random.andThen + (\boolResult -> + if boolResult then + Random.map Just generator + + else + Random.constant Nothing + ) + +generate 6 (Random.int 0 1 |> failHalfOfTheTime) + --> [Nothing, Just 1, Just 0, Nothing, Just 1, Nothing] +``` + +It is sometimes useful to define a generator self-recursively. +In those cases, you might need to use `Random.lazy` to keep the compiler from unrolling the generator infinitely. + +```elm +type Peano + = Zero + | Next Peano + + +peano : Generator Peano +peano = + Random.uniform (Random.constant Zero) + [ Random.map Next (Random.lazy (\_ -> peano)) + ] + |> Random.andThen identity + +generate 12 peano + --> [Zero, Next(Next(Zero)), Zero, Next(Zero), Zero, Zero, Next(Zero), Zero, Zero, Next(Zero), Next(Next(Next(Zero)))] +``` + +This example is a little heavy, so let's break it down. + +`Peano` is a type that represents positive integers: `Zero` is 0, `Next(Zero)` is 1, `Next(Next(Zero))` is 2, etc. +We define `peano` to give us a random `Peano` number. + +First of all, note that `Random.lazy` doesn't add anything to the logic, it merely prevents the compiler from writing `peano` into `peano` indefinitely. + +We use `Random.uniform` to pick between zero (with 50% probability) and another `Peano` number plus one (with 50% probability). +However, unlike with the previous example, `Random.uniform` is not picking from values (like `Zero`) but instead from generators (like `Random.constant Zero`) since we want to use `peano` itself. +This means that `Random.uniform` will return a value of type `Generator (Generator Peano)`, which is not want we need. + +To "flatten" the generator, we pipe it into `Random.andThen : (a -> Generator b) -> Generator a -> Generator b`, where we want `b` to be `Peano`. +Since `Generator a` is `Generator (Generator Peano)`, `a` must be `Generator Peano`, and the function `(a -> Generator b)` must be of type `(Generator Peano -> Generator Peano)`. +We don't want to modify anything, so `identity` is the right choice. + +Finally, what kind of numbers will `peano` produce? +We know that it will produce 0 50% of the time, or another iteration of itself plus one 50% of the time. +That means the numbers will be 0 (50% of the time), 1 (25% of the time), 2 (12% of the time), 3 (6% of the time), etc. +`peano` can produce arbitrary large numbers, but with exponentially decreasing probability. diff --git a/exercises/concept/squeaky-clean/.docs/introduction.md b/exercises/concept/squeaky-clean/.docs/introduction.md index f64eeffa..a26d70ec 100644 --- a/exercises/concept/squeaky-clean/.docs/introduction.md +++ b/exercises/concept/squeaky-clean/.docs/introduction.md @@ -1,15 +1,17 @@ # Introduction +## Strings + Characters and Strings form the basis for text representation in Elm. Strings can be thought of as lists of characters and most `List` functions have their equivalent in the `String` module. -## Characters +### Characters The `Char` type represents a single unicode character with single quotes e.g. `'a'`. The `Char` module provides predicate functions (`Char -> Bool`) to test qualities of a character: `isUpper`, `isLower`, `isAlpha`, and `isAlphaNum` The module also provides functions to convert a character `toUpper`, `toLower`, `toLocaleUpper`, `toLocaleLower`, as well as to/from their numerical unicode value (`Int`) with `toCode` and `fromCode`. -## Strings +### Strings The `String` type provides a built-in representation for efficient string manipulation and can represent any sequence of unicode characters e.g. "this is an Elm string." Multi-line strings are represented with triple double quotes and can have unescaped quotes and newlines. @@ -21,7 +23,7 @@ are preserved. """ ``` -## String Functions +### String Functions The `String` module contains all the functions summarized on the table below. Some of the most commonly used functions include: @@ -80,7 +82,7 @@ map (\c -> if c == '/' then '.' else c) "a/b/c" --> "a.b.c" ``` -## All String Functions: +### All String Functions: | Manipulate | Substrings | Check | Convert | Higher Order | |------------|------------|-------------|--------------|--------------| | `reverse` | `slice` | `length` | `toInt` | `map` | @@ -93,4 +95,4 @@ map (\c -> if c == '/' then '.' else c) "a/b/c" | `words` | | | | | | `lines` | | | | | | `cons` | | | | | -| `uncons` | | | | | \ No newline at end of file +| `uncons` | | | | |