From d5bdff9743cc1a62f1f5d8bc8375c60e85afd9a2 Mon Sep 17 00:00:00 2001 From: Bobbi Towers Date: Fri, 13 Oct 2023 11:19:12 -0700 Subject: [PATCH] Add Coordinate Transformation learning exercise (#587) * remove space * change quote to backtick * remove space in code block * remove translate2d function from instructions * convert JavaScript calls to Clojure syntax * put code output on new line * provide additional line break to accomodate narrow width * link to function closures doc in general hints * update github user name * add closures and atoms concepts * add atoms to coordinate transformation concepts array * add atoms and closures concepts to config.json --- concepts/atoms/.meta/config.json | 6 +++ concepts/atoms/about.md | 29 ++++++++++++++ concepts/atoms/introduction.md | 29 ++++++++++++++ concepts/atoms/links.json | 6 +++ concepts/closures/.meta/config.json | 4 ++ concepts/closures/about.md | 38 +++++++++++++++++++ concepts/closures/introduction.md | 38 +++++++++++++++++++ concepts/closures/links.json | 10 +++++ config.json | 16 ++++++-- .../coordinate-transformation/.docs/hints.md | 5 ++- .../.docs/instructions.md | 21 +++++----- .../.docs/introduction.md | 6 +-- .../.meta/config.json | 2 +- 13 files changed, 190 insertions(+), 20 deletions(-) create mode 100644 concepts/atoms/.meta/config.json create mode 100644 concepts/atoms/about.md create mode 100644 concepts/atoms/introduction.md create mode 100644 concepts/atoms/links.json create mode 100644 concepts/closures/.meta/config.json create mode 100644 concepts/closures/about.md create mode 100644 concepts/closures/introduction.md create mode 100644 concepts/closures/links.json diff --git a/concepts/atoms/.meta/config.json b/concepts/atoms/.meta/config.json new file mode 100644 index 000000000..53c5fe9f0 --- /dev/null +++ b/concepts/atoms/.meta/config.json @@ -0,0 +1,6 @@ +{ + "blurb": "Atoms in Clojure are a reference type for managing shared state.", + "authors": [ + "bobbicodes" + ] +} \ No newline at end of file diff --git a/concepts/atoms/about.md b/concepts/atoms/about.md new file mode 100644 index 000000000..6949451bb --- /dev/null +++ b/concepts/atoms/about.md @@ -0,0 +1,29 @@ +# About + +Since all of Clojure's standard data types are immutable, it offers *reference types* which offer controlled mutation. The simplest and most commonly used of these are called Atoms. + +Atoms are created with the `atom` function, which take an initial value: + +```clojure +(def players (atom ())) +``` + +The current value of the atom is dereferenced with either `deref` or the `@` shorthand: + +```clojure +@players +;;=> () +``` + +The current value of the `players` atom can be modified using `swap!`, which updates the value by applying a function: + +```clojure +(swap! players conj :player1) +;;=> (:player1) +``` + +The `reset!` function replaces the value of an atom without regard for its current value: + +```clojure +(reset! players ()) +``` diff --git a/concepts/atoms/introduction.md b/concepts/atoms/introduction.md new file mode 100644 index 000000000..6ec5152da --- /dev/null +++ b/concepts/atoms/introduction.md @@ -0,0 +1,29 @@ +# Introduction + +Since all of Clojure's standard data types are immutable, it offers *reference types* which offer controlled mutation. The simplest and most commonly used of these are called Atoms. + +Atoms are created with the `atom` function, which take an initial value: + +```clojure +(def players (atom ())) +``` + +The current value of the atom is dereferenced with either `deref` or the `@` shorthand: + +```clojure +@players +;;=> () +``` + +The current value of the `players` atom can be modified using `swap!`, which updates the value by applying a function: + +```clojure +(swap! players conj :player1) +;;=> (:player1) +``` + +The `reset!` function replaces the value of an atom without regard for its current value: + +```clojure +(reset! players ()) +``` diff --git a/concepts/atoms/links.json b/concepts/atoms/links.json new file mode 100644 index 000000000..64317fcab --- /dev/null +++ b/concepts/atoms/links.json @@ -0,0 +1,6 @@ +[ + { + "url": "https://clojure.org/reference/atoms", + "description": "Clojure reference: Atoms" + } +] \ No newline at end of file diff --git a/concepts/closures/.meta/config.json b/concepts/closures/.meta/config.json new file mode 100644 index 000000000..499195053 --- /dev/null +++ b/concepts/closures/.meta/config.json @@ -0,0 +1,4 @@ +{ + "blurb": "Clojure transparently supports closures, that means variables from an outer scope can also be used inside of a function.", + "authors": ["bobbicodes"] + } \ No newline at end of file diff --git a/concepts/closures/about.md b/concepts/closures/about.md new file mode 100644 index 000000000..a06c9a8b9 --- /dev/null +++ b/concepts/closures/about.md @@ -0,0 +1,38 @@ +# About + +**Closures** are a programming pattern [in Clojure][clojure-guide-closures] which allows variables from an outer [lexical scope][wiki-lexical-scope] to be used inside of a function. Clojure supports closures transparently, and they are often used without knowing what they are. + +```clojure +;; Top-level definitions are global-scope +(def dozen 12) + +;; Functions create a new scope. +;; Referencing the outer variable here is a closure. +(fn [n] (* dozen n)) +``` + +## Closures to save state and pass along values + +Using an atom allows for some state to be preserved: + +```clojure +;; This function closure increments the counter's state +;; in the outer lexical context. +;; This way the counter can be shared between many calling contexts. + +(def increment + (let [counter (atom 0)] + (fn [] (swap! counter inc)))) +``` + +Each successive call to `increment` increments its counter: + +``` clojure +(increment) +;;=> 1 +(increment) +;;=> 2 +``` + +[wiki-lexical-scope]: https://en.wikipedia.org/wiki/Scope_(computer_science)#Lexical_scoping +[clojure-guide-closures]: https://clojure.org/guides/higher_order_functions#_functions_returning_functions_and_closures \ No newline at end of file diff --git a/concepts/closures/introduction.md b/concepts/closures/introduction.md new file mode 100644 index 000000000..992be2c12 --- /dev/null +++ b/concepts/closures/introduction.md @@ -0,0 +1,38 @@ +# Introduction + +**Closures** are a programming pattern [in Clojure][clojure-guide-closures] which allows variables from an outer [lexical scope][wiki-lexical-scope] to be used inside of a function. Clojure supports closures transparently, and they are often used without knowing what they are. + +```clojure +;; Top-level definitions are global-scope +(def dozen 12) + +;; Functions create a new scope. +;; Referencing the outer variable here is a closure. +(fn [n] (* dozen n)) +``` + +## Closures to save state and pass along values + +Using an atom allows for some state to be preserved: + +```clojure +;; This function closure increments the counter's state +;; in the outer lexical context. +;; This way the counter can be shared between many calling contexts. + +(def increment + (let [counter (atom 0)] + (fn [] (swap! counter inc)))) +``` + +Each successive call to `increment` increments its counter: + +``` clojure +(increment) +;;=> 1 +(increment) +;;=> 2 +``` + +[wiki-lexical-scope]: https://en.wikipedia.org/wiki/Scope_(computer_science)#Lexical_scoping +[clojure-guide-closures]: https://clojure.org/guides/higher_order_functions#_functions_returning_functions_and_closures \ No newline at end of file diff --git a/concepts/closures/links.json b/concepts/closures/links.json new file mode 100644 index 000000000..506acea0d --- /dev/null +++ b/concepts/closures/links.json @@ -0,0 +1,10 @@ +[ + { + "url": "https://clojure.org/guides/higher_order_functions#_functions_returning_functions_and_closures", + "description": "Clojure guide: Functions returning functions and closures" + }, + { + "url": "https://en.wikipedia.org/wiki/Closure_(computer_programming)", + "description": "Wikipedia: Closure" + } + ] \ No newline at end of file diff --git a/config.json b/config.json index 46c939bd7..c3590b173 100644 --- a/config.json +++ b/config.json @@ -154,9 +154,9 @@ "slug": "coordinate-transformation", "name": "Coordinate Transformation", "uuid": "5b23eabd-6ebb-4fed-b889-9324ad174a5f", - "concepts": [], - "prerequisites": [], - "status": "wip" + "concepts": ["closures", "atoms"], + "prerequisites": ["vectors"], + "status": "beta" }, { "slug": "card-games", @@ -1077,6 +1077,16 @@ "uuid": "268d79d7-1bdd-41a5-a7a9-275332d3967c", "slug": "sequential-destructuring", "name": "Sequential Destructuring" + }, + { + "uuid": "6693b4e0-bba7-465c-b1f8-0ddb00fc3d23", + "slug": "atoms", + "name": "Atoms" + }, + { + "uuid": "75d16185-4ca9-49d1-969f-3dd6fc377b96", + "slug": "closures", + "name": "Closures" } ], "key_features": [ diff --git a/exercises/concept/coordinate-transformation/.docs/hints.md b/exercises/concept/coordinate-transformation/.docs/hints.md index 81592baf6..aa6e03833 100644 --- a/exercises/concept/coordinate-transformation/.docs/hints.md +++ b/exercises/concept/coordinate-transformation/.docs/hints.md @@ -2,7 +2,7 @@ ## General -- For each task, each function should return a function closure, using the supplied arguments. +- For each task, each function should return a function closure, using the supplied arguments. See the section of the Clojure docs on [function closures][closures] for reference. ## 1. Translate the coordinates @@ -23,4 +23,5 @@ - In order to send back the result of the last transformation, you will have to check if the input arguments are the same. - To save the value of the arguments and the last result, you can use an [atom][atoms]. -[atoms]: https://clojure.org/reference/atoms \ No newline at end of file +[atoms]: https://clojure.org/reference/atoms +[closures]: https://clojure.org/guides/higher_order_functions#_functions_returning_functions_and_closures \ No newline at end of file diff --git a/exercises/concept/coordinate-transformation/.docs/instructions.md b/exercises/concept/coordinate-transformation/.docs/instructions.md index 06f143fca..1b920fa57 100644 --- a/exercises/concept/coordinate-transformation/.docs/instructions.md +++ b/exercises/concept/coordinate-transformation/.docs/instructions.md @@ -9,13 +9,6 @@ so you decide to use a function closure to create reusable transformations for ` Implement the `translate2d` function that returns a function making use of a closure to perform a repeatable 2d translation of a coordinate pair. ```clojure - -(defn translate2d - "Returns a function making use of a closure to - perform a repeatable 2d translation of a coordinate pair." - [dx dy] - (fn [x y] [(+ dx x) (+ dy y)])) - (def move-coordinates-right-2px (translate2d 2 0)) (def result (move-coordinates-right-2px 4 8)) ;; result => [6 8] @@ -57,12 +50,18 @@ Implement the `memoize-transform` function. It takes a function to _memoize_, th (def triple-scale (scale2d 3 3)) (def memoized-scale (memoize-transform triplescale)) -(memoized-scale 4 3) ;; => [12, 9], this is computed since it hasn't been computed before for the arguments -(memoized-scale 4 3) ;; => [12, 9], this is remembered, since it was computed already +(memoized-scale 4 3) +;; => [12, 9], this is computed since it hasn't been computed before for the arguments + +(memoized-scale 4 3) +;; => [12, 9], this is remembered, since it was computed already (def triple-scale (scale2d 3 3)) (def memoized-scale (memoize-transform triple-scale)) -memoizedScale(4, 3) ;; // => [12, 9], this is computed since it hasn't been computed before for the arguments -memoizedScale(4, 3) ;; // => [12, 9], this is remembered, since it was computed already +(memoizedScale 4 3) +;; => [12, 9], this is computed since it hasn't been computed before for the arguments + +(memoizedScale 4 3) +;; => [12, 9], this is remembered, since it was computed already ``` \ No newline at end of file diff --git a/exercises/concept/coordinate-transformation/.docs/introduction.md b/exercises/concept/coordinate-transformation/.docs/introduction.md index 56b5b61bb..992be2c12 100644 --- a/exercises/concept/coordinate-transformation/.docs/introduction.md +++ b/exercises/concept/coordinate-transformation/.docs/introduction.md @@ -16,8 +16,8 @@ Using an atom allows for some state to be preserved: ```clojure - -;; This function closure increments the counter's state in the outer lexical context. +;; This function closure increments the counter's state +;; in the outer lexical context. ;; This way the counter can be shared between many calling contexts. (def increment @@ -25,7 +25,7 @@ Using an atom allows for some state to be preserved: (fn [] (swap! counter inc)))) ``` -Each successive call to `increment' increments its counter: +Each successive call to `increment` increments its counter: ``` clojure (increment) diff --git a/exercises/concept/coordinate-transformation/.meta/config.json b/exercises/concept/coordinate-transformation/.meta/config.json index 5af7debad..630754b27 100644 --- a/exercises/concept/coordinate-transformation/.meta/config.json +++ b/exercises/concept/coordinate-transformation/.meta/config.json @@ -1,7 +1,7 @@ { "blurb": "Practice your knowledge of closures by implementing various coordinate transformations.", "authors": [ - "porkostomus" + "bobbicodes" ], "forked_from": [ "javascript/coordinate-transformation"