Skip to content

Commit ab20070

Browse files
authored
react (#66)
1 parent d50308c commit ab20070

File tree

8 files changed

+292
-0
lines changed

8 files changed

+292
-0
lines changed

config.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -510,6 +510,14 @@
510510
"practices": [],
511511
"prerequisites": [],
512512
"difficulty": 8
513+
},
514+
{
515+
"slug": "react",
516+
"name": "React",
517+
"uuid": "ea4b0ade-0c71-44b2-99ec-9321fffe19ff",
518+
"practices": [],
519+
"prerequisites": [],
520+
"difficulty": 8
513521
}
514522
]
515523
},

exercises/practice/react/.busted

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
return {
2+
default = {
3+
ROOT = { '.' }
4+
}
5+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Instructions
2+
3+
Implement a basic reactive system.
4+
5+
Reactive programming is a programming paradigm that focuses on how values are computed in terms of each other to allow a change to one value to automatically propagate to other values, like in a spreadsheet.
6+
7+
Implement a basic reactive system with cells with settable values ("input" cells) and cells with values computed in terms of other cells ("compute" cells).
8+
Implement updates so that when an input value is changed, values propagate to reach a new stable system state.
9+
10+
In addition, compute cells should allow for registering change notification callbacks.
11+
Call a cell’s callbacks when the cell’s value in a new stable state has changed from the previous stable state.
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"authors": [
3+
"glennj"
4+
],
5+
"files": {
6+
"solution": [
7+
"react.moon"
8+
],
9+
"test": [
10+
"react_spec.moon"
11+
],
12+
"example": [
13+
".meta/example.moon"
14+
]
15+
},
16+
"blurb": "Implement a basic reactive system."
17+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
class Cell
2+
new: =>
3+
@val = nil
4+
@listeners = {}
5+
6+
get_value: => @val
7+
8+
store_value: (value) => @val = value
9+
10+
add_listener: (listener) =>
11+
table.insert @listeners, listener
12+
13+
recompute_listeners: =>
14+
listener\recompute! for listener in *@listeners
15+
16+
fire_listener_callbacks: =>
17+
listener\fire_callbacks! for listener in *@listeners
18+
19+
20+
class InputCell extends Cell
21+
new: (value) =>
22+
super!
23+
@val = value
24+
25+
set_value: (value) =>
26+
@store_value value
27+
@recompute_listeners!
28+
@fire_listener_callbacks!
29+
30+
31+
class ComputeCell extends Cell
32+
new: (...) =>
33+
super!
34+
@inputs = {...}
35+
@formula = table.remove @inputs
36+
@callbacks = {}
37+
38+
input\add_listener self for input in *@inputs
39+
@compute!
40+
@previous = @get_value!
41+
42+
compute: =>
43+
values = [input\get_value! for input in *@inputs]
44+
-- note the odd parentheses: have to extract the formula from the instance *first*
45+
@store_value (@formula) table.unpack values
46+
47+
recompute: =>
48+
@compute!
49+
@recompute_listeners!
50+
51+
fire_callbacks: =>
52+
val = @get_value!
53+
return if val == @previous
54+
55+
@previous = val
56+
cb val for cb in *@callbacks
57+
@fire_listener_callbacks!
58+
59+
watch: (callback) =>
60+
table.insert @callbacks, callback
61+
62+
unwatch: (callback) =>
63+
for i, cb in ipairs @callbacks
64+
if cb == callback
65+
table.remove @callbacks, i
66+
break
67+
68+
69+
{ :InputCell, :ComputeCell }
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# This is an auto-generated file.
2+
#
3+
# Regenerating this file via `configlet sync` will:
4+
# - Recreate every `description` key/value pair
5+
# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications
6+
# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion)
7+
# - Preserve any other key/value pair
8+
#
9+
# As user-added comments (using the # character) will be removed when this file
10+
# is regenerated, comments can be added via a `comment` key.
11+
12+
[c51ee736-d001-4f30-88d1-0c8e8b43cd07]
13+
description = "input cells have a value"
14+
15+
[dedf0fe0-da0c-4d5d-a582-ffaf5f4d0851]
16+
description = "an input cell's value can be set"
17+
18+
[5854b975-f545-4f93-8968-cc324cde746e]
19+
description = "compute cells calculate initial value"
20+
21+
[25795a3d-b86c-4e91-abe7-1c340e71560c]
22+
description = "compute cells take inputs in the right order"
23+
24+
[c62689bf-7be5-41bb-b9f8-65178ef3e8ba]
25+
description = "compute cells update value when dependencies are changed"
26+
27+
[5ff36b09-0a88-48d4-b7f8-69dcf3feea40]
28+
description = "compute cells can depend on other compute cells"
29+
30+
[abe33eaf-68ad-42a5-b728-05519ca88d2d]
31+
description = "compute cells fire callbacks"
32+
33+
[9e5cb3a4-78e5-4290-80f8-a78612c52db2]
34+
description = "callback cells only fire on change"
35+
36+
[ada17cb6-7332-448a-b934-e3d7495c13d3]
37+
description = "callbacks do not report already reported values"
38+
39+
[ac271900-ea5c-461c-9add-eeebcb8c03e5]
40+
description = "callbacks can fire from multiple cells"
41+
42+
[95a82dcc-8280-4de3-a4cd-4f19a84e3d6f]
43+
description = "callbacks can be added and removed"
44+
45+
[f2a7b445-f783-4e0e-8393-469ab4915f2a]
46+
description = "removing a callback multiple times doesn't interfere with other callbacks"
47+
48+
[daf6feca-09e0-4ce5-801d-770ddfe1c268]
49+
description = "callbacks should only be called once even if multiple dependencies change"
50+
51+
[9a5b159f-b7aa-4729-807e-f1c38a46d377]
52+
description = "callbacks should not be called if dependencies change but output value doesn't change"
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
-- This exercise is rated "difficult".
2+
-- You are expected to code what is needed by the tests.
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
import InputCell, ComputeCell from require 'react'
2+
3+
describe 'react', ->
4+
it 'input cells have a value', ->
5+
input = InputCell 2
6+
assert.are.equal 2, input\get_value!
7+
8+
pending "an input cell's value can be set", ->
9+
input = InputCell 4
10+
input\set_value 20
11+
assert.are.equal 20, input\get_value!
12+
13+
pending 'compute cells calculate initial value', ->
14+
input = InputCell 1
15+
output = ComputeCell input, (x) -> x + 1
16+
assert.are.equal 2, output\get_value!
17+
18+
pending 'compute cells take inputs in the right order', ->
19+
one = InputCell 1
20+
two = InputCell 2
21+
output = ComputeCell one, two, (x, y) -> x + y * 10
22+
assert.are.equal 21, output\get_value!
23+
24+
pending 'compute cells update value when dependencies are changed', ->
25+
input = InputCell 1
26+
output = ComputeCell input, (x) -> x + 1
27+
28+
input\set_value 3
29+
assert.are.equal 4, output\get_value!
30+
31+
pending 'compute cells can depend on other compute cells', ->
32+
input = InputCell 1
33+
times_two = ComputeCell input, (x) -> x * 2
34+
times_thirty = ComputeCell input, (x) -> x * 30
35+
36+
output = ComputeCell times_two, times_thirty, (x, y) -> x + y
37+
assert.are.equal 32, output\get_value!
38+
39+
input\set_value 3
40+
assert.are.equal 96, output\get_value!
41+
42+
pending 'compute cells fire callbacks', ->
43+
input = InputCell 1
44+
output = ComputeCell input, (x) -> x + 1
45+
46+
-- "Spies" are documented here: https://lunarmodules.github.io/busted/#spies-mocks-stubs
47+
callback = spy.new(->) -- the argument is an empty function
48+
49+
output\watch callback
50+
input\set_value 3
51+
assert.spy(callback).was_called 1
52+
assert.spy(callback).was_called_with 4
53+
54+
pending 'callbacks only fire on change', ->
55+
input = InputCell 1
56+
output = ComputeCell input, (x) -> if x < 3 then 111 else 222
57+
callback = spy.new(->)
58+
59+
output\watch callback
60+
61+
input\set_value 2
62+
assert.spy(callback).was_called 0
63+
64+
input\set_value 4
65+
assert.spy(callback).was_called 1
66+
assert.spy(callback).was_called_with 222
67+
68+
pending 'callbacks can be added and removed', ->
69+
input = InputCell 11
70+
output = ComputeCell input, (x) -> x + 1
71+
callback1 = spy.new(->)
72+
callback2 = spy.new(->)
73+
callback3 = spy.new(->)
74+
75+
output\watch callback1
76+
output\watch callback2
77+
input\set_value 31
78+
79+
output\unwatch callback1
80+
output\watch callback3
81+
input\set_value 41
82+
83+
assert.spy(callback1).was_called 1
84+
assert.spy(callback1).was_called_with 32
85+
assert.spy(callback2).was_called 2
86+
assert.spy(callback2).was_called_with 42
87+
assert.spy(callback3).was_called 1
88+
assert.spy(callback3).was_called_with 42
89+
90+
pending "removing a callback multiple times doesn't interfere with other callbacks", ->
91+
input = InputCell 1
92+
output = ComputeCell input, (x) -> x + 1
93+
callback1 = spy.new(->)
94+
callback2 = spy.new(->)
95+
96+
output\watch callback1
97+
output\watch callback2
98+
for i = 1, 10
99+
output\unwatch callback1
100+
input\set_value 2
101+
102+
assert.spy(callback1).was_called 0
103+
assert.spy(callback2).was_called 1
104+
assert.spy(callback2).was_called_with 3
105+
106+
pending 'callbacks only called once even if multiple inputs change', ->
107+
input = InputCell 1
108+
plus_one = ComputeCell input, (x) -> x + 1
109+
minus_one1 = ComputeCell input, (x) -> x - 1
110+
minus_one2 = ComputeCell minus_one1, (x) -> x - 1
111+
output = ComputeCell plus_one, minus_one2, (x, y) -> x * y
112+
callback = spy.new(->)
113+
114+
output\watch callback
115+
input\set_value 4
116+
assert.spy(callback).was_called 1
117+
assert.spy(callback).was_called_with 10
118+
119+
pending "callbacks not called if inputs change but output doesn't", ->
120+
input = InputCell 1
121+
plus_one = ComputeCell input, (x) -> x + 1
122+
minus_one = ComputeCell input, (x) -> x - 1
123+
always_two = ComputeCell plus_one, minus_one, (x, y) -> x - y
124+
callback = spy.new(->)
125+
126+
always_two\watch callback
127+
input\set_value i for i = 1, 10
128+
assert.spy(callback).was_called 0

0 commit comments

Comments
 (0)