Skip to content

Commit eb8e11e

Browse files
committed
react: complete exercise
1 parent 0ac2ed4 commit eb8e11e

File tree

4 files changed

+169
-1
lines changed

4 files changed

+169
-1
lines changed

exercises/react/example/Reactor.ceylon

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import ceylon.collection { HashMap, MutableMap }
2+
13
alias Element => Integer;
24

35
class Reactor() {
@@ -20,6 +22,7 @@ class Reactor() {
2022
assign currentValue {
2123
currentValue_ = currentValue;
2224
eachDependent((d) => d.propagate());
25+
eachDependent((d) => d.fireCallbacks());
2326
}
2427
}
2528

@@ -28,16 +31,46 @@ class Reactor() {
2831
}
2932

3033
shared class ComputeCell(Element() newValue) extends Cell() {
34+
shared alias Callback => Anything(Element);
35+
3136
variable Element currentValue_ = newValue();
37+
variable Element lastCallbackValue = currentValue_;
3238
shared actual Element currentValue => currentValue_;
3339

40+
variable Integer callbacksIssued = 0;
41+
variable MutableMap<Integer, Callback> activeCallbacks = HashMap<Integer, Callback>();
42+
43+
shared interface Subscription {
44+
shared formal void cancel();
45+
}
46+
47+
shared Subscription addCallback(Callback f) {
48+
value id = callbacksIssued;
49+
callbacksIssued++;
50+
activeCallbacks.put(id, f);
51+
return object satisfies Subscription {
52+
cancel() => activeCallbacks.remove(id);
53+
};
54+
}
55+
3456
shared void propagate() {
3557
value nv = newValue();
3658
if (nv != currentValue) {
3759
currentValue_ = nv;
3860
eachDependent((d) => d.propagate());
3961
}
4062
}
63+
64+
shared void fireCallbacks() {
65+
if (lastCallbackValue == currentValue) {
66+
return;
67+
}
68+
lastCallbackValue = currentValue;
69+
for (cb in activeCallbacks.items) {
70+
cb(currentValue);
71+
}
72+
eachDependent((d) => d.fireCallbacks());
73+
}
4174
}
4275

4376
shared ComputeCell newComputeCell1(Cell c, Element(Element) f) {

exercises/react/example/module.ceylon

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
module react "1.0" {
2+
import "ceylon.collection" "1.3.1";
3+
import "ceylon.test" "1.3.1";
4+
}
Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,37 @@
1-
// TODO
1+
alias Element => Integer;
2+
3+
class Reactor() {
4+
shared abstract class Cell() {
5+
shared formal Element currentValue;
6+
}
7+
8+
shared class InputCell(Element initialValue) extends Cell() {
9+
shared actual variable Element currentValue = nothing;
10+
}
11+
12+
shared InputCell newInputCell(Element initialValue) {
13+
return nothing;
14+
}
15+
16+
shared class ComputeCell(Element() newValue) extends Cell() {
17+
shared alias Callback => Anything(Element);
18+
19+
shared actual Element currentValue = nothing;
20+
21+
shared interface Subscription {
22+
shared formal void cancel();
23+
}
24+
25+
shared Subscription addCallback(Callback f) {
26+
return nothing;
27+
}
28+
}
29+
30+
shared ComputeCell newComputeCell1(Cell c, Element(Element) f) {
31+
return nothing;
32+
}
33+
34+
shared ComputeCell newComputeCell2(Cell c1, Cell c2, Element(Element, Element) f) {
35+
return nothing;
36+
}
37+
}

exercises/react/source/react/ReactorTest.ceylon

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,98 @@ void computeCellsUpdateValueWhenDependenciesChange() {
4040
input.currentValue = 3;
4141
assertEquals(output.currentValue, 4);
4242
}
43+
44+
test
45+
void computeCellsCanDependOnOtherComputeCells() {
46+
value r = Reactor();
47+
value input = r.newInputCell(1);
48+
value timesTwo = r.newComputeCell1(input, (x) => x * 2);
49+
value timesThirty = r.newComputeCell1(input, (x) => x * 30);
50+
value output = r.newComputeCell2(timesTwo, timesThirty, (x, y) => x + y);
51+
52+
assertEquals(output.currentValue, 32);
53+
input.currentValue = 3;
54+
assertEquals(output.currentValue, 96);
55+
}
56+
57+
test
58+
void computeCellsFireCallbacks() {
59+
value r = Reactor();
60+
value input = r.newInputCell(1);
61+
value output = r.newComputeCell1(input, (x) => x + 1);
62+
63+
variable Element[] vals = [];
64+
output.addCallback((x) => vals = vals.withTrailing(x));
65+
66+
input.currentValue = 3;
67+
assertEquals(vals, [4]);
68+
}
69+
70+
test
71+
void callbacksOnlyFireOnChange() {
72+
value r = Reactor();
73+
value input = r.newInputCell(1);
74+
value output = r.newComputeCell1(input, (x) => if (x < 3) then 111 else 222);
75+
76+
variable Element[] vals = [];
77+
output.addCallback((x) => vals = vals.withTrailing(x));
78+
79+
input.currentValue = 2;
80+
assertEquals(vals, []);
81+
82+
input.currentValue = 4;
83+
assertEquals(vals, [222]);
84+
}
85+
86+
test
87+
void removingCallbackMultipleTimesDoesntInterfereWithOtherCallbacks() {
88+
value r = Reactor();
89+
value input = r.newInputCell(1);
90+
value output = r.newComputeCell1(input, (x) => x + 1);
91+
92+
variable Element[] vals1 = [];
93+
value sub1 = output.addCallback((x) => vals1 = vals1.withTrailing(x));
94+
variable Element[] vals2 = [];
95+
output.addCallback((x) => vals2 = vals2.withTrailing(x));
96+
97+
for (i in 1..10) {
98+
sub1.cancel();
99+
}
100+
101+
input.currentValue = 2;
102+
assertEquals(vals1, []);
103+
assertEquals(vals2, [3]);
104+
}
105+
106+
test
107+
void callbacksAreOnlyCalledOnceEvenIfMultipleDependenciesChange() {
108+
value r = Reactor();
109+
value input = r.newInputCell(1);
110+
value plusOne = r.newComputeCell1(input, (x) => x + 1);
111+
value minusOne1 = r.newComputeCell1(input, (x) => x - 1);
112+
value minusOne2 = r.newComputeCell1(minusOne1, (x) => x - 1);
113+
value output = r.newComputeCell2(plusOne, minusOne2, (x, y) => x * y);
114+
115+
variable Element[] vals = [];
116+
output.addCallback((x) => vals = vals.withTrailing(x));
117+
118+
input.currentValue = 4;
119+
assertEquals(vals, [10]);
120+
}
121+
122+
test
123+
void callbacksAreNotCalledIfDependenciesChangeButOutputValueDoesntChange() {
124+
value r = Reactor();
125+
value input = r.newInputCell(1);
126+
value plusOne = r.newComputeCell1(input, (x) => x + 1);
127+
value minusOne = r.newComputeCell1(input, (x) => x - 1);
128+
value alwaysTwo = r.newComputeCell2(plusOne, minusOne, (x, y) => x - y);
129+
130+
variable Element[] vals = [];
131+
alwaysTwo.addCallback((x) => vals = vals.withTrailing(x));
132+
133+
for (i in 1..10) {
134+
input.currentValue = i;
135+
}
136+
assertEquals(vals, []);
137+
}

0 commit comments

Comments
 (0)