Skip to content

Commit

Permalink
Add Coordinate Transformation learning exercise (#587)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
bobbicodes authored Oct 13, 2023
1 parent b1ca98c commit d5bdff9
Show file tree
Hide file tree
Showing 13 changed files with 190 additions and 20 deletions.
6 changes: 6 additions & 0 deletions concepts/atoms/.meta/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"blurb": "Atoms in Clojure are a reference type for managing shared state.",
"authors": [
"bobbicodes"
]
}
29 changes: 29 additions & 0 deletions concepts/atoms/about.md
Original file line number Diff line number Diff line change
@@ -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 ())
```
29 changes: 29 additions & 0 deletions concepts/atoms/introduction.md
Original file line number Diff line number Diff line change
@@ -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 ())
```
6 changes: 6 additions & 0 deletions concepts/atoms/links.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[
{
"url": "https://clojure.org/reference/atoms",
"description": "Clojure reference: Atoms"
}
]
4 changes: 4 additions & 0 deletions concepts/closures/.meta/config.json
Original file line number Diff line number Diff line change
@@ -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"]
}
38 changes: 38 additions & 0 deletions concepts/closures/about.md
Original file line number Diff line number Diff line change
@@ -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
38 changes: 38 additions & 0 deletions concepts/closures/introduction.md
Original file line number Diff line number Diff line change
@@ -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
10 changes: 10 additions & 0 deletions concepts/closures/links.json
Original file line number Diff line number Diff line change
@@ -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"
}
]
16 changes: 13 additions & 3 deletions config.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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": [
Expand Down
5 changes: 3 additions & 2 deletions exercises/concept/coordinate-transformation/.docs/hints.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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
[atoms]: https://clojure.org/reference/atoms
[closures]: https://clojure.org/guides/higher_order_functions#_functions_returning_functions_and_closures
21 changes: 10 additions & 11 deletions exercises/concept/coordinate-transformation/.docs/instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -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
```
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,16 @@
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
(let [counter (atom 0)]
(fn [] (swap! counter inc))))
```

Each successive call to `increment' increments its counter:
Each successive call to `increment` increments its counter:

``` clojure
(increment)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"blurb": "Practice your knowledge of closures by implementing various coordinate transformations.",
"authors": [
"porkostomus"
"bobbicodes"
],
"forked_from": [
"javascript/coordinate-transformation"
Expand Down

0 comments on commit d5bdff9

Please sign in to comment.