Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions config.json
Original file line number Diff line number Diff line change
Expand Up @@ -510,6 +510,14 @@
"practices": [],
"prerequisites": [],
"difficulty": 8
},
{
"slug": "react",
"name": "React",
"uuid": "ea4b0ade-0c71-44b2-99ec-9321fffe19ff",
"practices": [],
"prerequisites": [],
"difficulty": 8
}
]
},
Expand Down
5 changes: 5 additions & 0 deletions exercises/practice/react/.busted
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
return {
default = {
ROOT = { '.' }
}
}
11 changes: 11 additions & 0 deletions exercises/practice/react/.docs/instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Instructions

Implement a basic reactive system.

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.

Implement a basic reactive system with cells with settable values ("input" cells) and cells with values computed in terms of other cells ("compute" cells).
Implement updates so that when an input value is changed, values propagate to reach a new stable system state.

In addition, compute cells should allow for registering change notification callbacks.
Call a cell’s callbacks when the cell’s value in a new stable state has changed from the previous stable state.
17 changes: 17 additions & 0 deletions exercises/practice/react/.meta/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"authors": [
"glennj"
],
"files": {
"solution": [
"react.moon"
],
"test": [
"react_spec.moon"
],
"example": [
".meta/example.moon"
]
},
"blurb": "Implement a basic reactive system."
}
69 changes: 69 additions & 0 deletions exercises/practice/react/.meta/example.moon
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
class Cell
new: =>
@val = nil
@listeners = {}

get_value: => @val

store_value: (value) => @val = value

add_listener: (listener) =>
table.insert @listeners, listener

recompute_listeners: =>
listener\recompute! for listener in *@listeners

fire_listener_callbacks: =>
listener\fire_callbacks! for listener in *@listeners


class InputCell extends Cell
new: (value) =>
super!
@val = value

set_value: (value) =>
@store_value value
@recompute_listeners!
@fire_listener_callbacks!


class ComputeCell extends Cell
new: (...) =>
super!
@inputs = {...}
@formula = table.remove @inputs
@callbacks = {}

input\add_listener self for input in *@inputs
@compute!
@previous = @get_value!

compute: =>
values = [input\get_value! for input in *@inputs]
-- note the odd parentheses: have to extract the formula from the instance *first*
@store_value (@formula) table.unpack values

recompute: =>
@compute!
@recompute_listeners!

fire_callbacks: =>
val = @get_value!
return if val == @previous

@previous = val
cb val for cb in *@callbacks
@fire_listener_callbacks!

watch: (callback) =>
table.insert @callbacks, callback

unwatch: (callback) =>
for i, cb in ipairs @callbacks
if cb == callback
table.remove @callbacks, i
break


{ :InputCell, :ComputeCell }
52 changes: 52 additions & 0 deletions exercises/practice/react/.meta/tests.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# This is an auto-generated file.
#
# Regenerating this file via `configlet sync` will:
# - Recreate every `description` key/value pair
# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications
# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion)
# - Preserve any other key/value pair
#
# As user-added comments (using the # character) will be removed when this file
# is regenerated, comments can be added via a `comment` key.

[c51ee736-d001-4f30-88d1-0c8e8b43cd07]
description = "input cells have a value"

[dedf0fe0-da0c-4d5d-a582-ffaf5f4d0851]
description = "an input cell's value can be set"

[5854b975-f545-4f93-8968-cc324cde746e]
description = "compute cells calculate initial value"

[25795a3d-b86c-4e91-abe7-1c340e71560c]
description = "compute cells take inputs in the right order"

[c62689bf-7be5-41bb-b9f8-65178ef3e8ba]
description = "compute cells update value when dependencies are changed"

[5ff36b09-0a88-48d4-b7f8-69dcf3feea40]
description = "compute cells can depend on other compute cells"

[abe33eaf-68ad-42a5-b728-05519ca88d2d]
description = "compute cells fire callbacks"

[9e5cb3a4-78e5-4290-80f8-a78612c52db2]
description = "callback cells only fire on change"

[ada17cb6-7332-448a-b934-e3d7495c13d3]
description = "callbacks do not report already reported values"

[ac271900-ea5c-461c-9add-eeebcb8c03e5]
description = "callbacks can fire from multiple cells"

[95a82dcc-8280-4de3-a4cd-4f19a84e3d6f]
description = "callbacks can be added and removed"

[f2a7b445-f783-4e0e-8393-469ab4915f2a]
description = "removing a callback multiple times doesn't interfere with other callbacks"

[daf6feca-09e0-4ce5-801d-770ddfe1c268]
description = "callbacks should only be called once even if multiple dependencies change"

[9a5b159f-b7aa-4729-807e-f1c38a46d377]
description = "callbacks should not be called if dependencies change but output value doesn't change"
2 changes: 2 additions & 0 deletions exercises/practice/react/react.moon
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
-- This exercise is rated "difficult".
-- You are expected to code what is needed by the tests.
128 changes: 128 additions & 0 deletions exercises/practice/react/react_spec.moon
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import InputCell, ComputeCell from require 'react'

describe 'react', ->
it 'input cells have a value', ->
input = InputCell 2
assert.are.equal 2, input\get_value!

pending "an input cell's value can be set", ->
input = InputCell 4
input\set_value 20
assert.are.equal 20, input\get_value!

pending 'compute cells calculate initial value', ->
input = InputCell 1
output = ComputeCell input, (x) -> x + 1
assert.are.equal 2, output\get_value!

pending 'compute cells take inputs in the right order', ->
one = InputCell 1
two = InputCell 2
output = ComputeCell one, two, (x, y) -> x + y * 10
assert.are.equal 21, output\get_value!

pending 'compute cells update value when dependencies are changed', ->
input = InputCell 1
output = ComputeCell input, (x) -> x + 1

input\set_value 3
assert.are.equal 4, output\get_value!

pending 'compute cells can depend on other compute cells', ->
input = InputCell 1
times_two = ComputeCell input, (x) -> x * 2
times_thirty = ComputeCell input, (x) -> x * 30

output = ComputeCell times_two, times_thirty, (x, y) -> x + y
assert.are.equal 32, output\get_value!

input\set_value 3
assert.are.equal 96, output\get_value!

pending 'compute cells fire callbacks', ->
input = InputCell 1
output = ComputeCell input, (x) -> x + 1

-- "Spies" are documented here: https://lunarmodules.github.io/busted/#spies-mocks-stubs
callback = spy.new(->) -- the argument is an empty function

output\watch callback
input\set_value 3
assert.spy(callback).was_called 1
assert.spy(callback).was_called_with 4

pending 'callbacks only fire on change', ->
input = InputCell 1
output = ComputeCell input, (x) -> if x < 3 then 111 else 222
callback = spy.new(->)

output\watch callback

input\set_value 2
assert.spy(callback).was_called 0

input\set_value 4
assert.spy(callback).was_called 1
assert.spy(callback).was_called_with 222

pending 'callbacks can be added and removed', ->
input = InputCell 11
output = ComputeCell input, (x) -> x + 1
callback1 = spy.new(->)
callback2 = spy.new(->)
callback3 = spy.new(->)

output\watch callback1
output\watch callback2
input\set_value 31

output\unwatch callback1
output\watch callback3
input\set_value 41

assert.spy(callback1).was_called 1
assert.spy(callback1).was_called_with 32
assert.spy(callback2).was_called 2
assert.spy(callback2).was_called_with 42
assert.spy(callback3).was_called 1
assert.spy(callback3).was_called_with 42

pending "removing a callback multiple times doesn't interfere with other callbacks", ->
input = InputCell 1
output = ComputeCell input, (x) -> x + 1
callback1 = spy.new(->)
callback2 = spy.new(->)

output\watch callback1
output\watch callback2
for i = 1, 10
output\unwatch callback1
input\set_value 2

assert.spy(callback1).was_called 0
assert.spy(callback2).was_called 1
assert.spy(callback2).was_called_with 3

pending 'callbacks only called once even if multiple inputs change', ->
input = InputCell 1
plus_one = ComputeCell input, (x) -> x + 1
minus_one1 = ComputeCell input, (x) -> x - 1
minus_one2 = ComputeCell minus_one1, (x) -> x - 1
output = ComputeCell plus_one, minus_one2, (x, y) -> x * y
callback = spy.new(->)

output\watch callback
input\set_value 4
assert.spy(callback).was_called 1
assert.spy(callback).was_called_with 10

pending "callbacks not called if inputs change but output doesn't", ->
input = InputCell 1
plus_one = ComputeCell input, (x) -> x + 1
minus_one = ComputeCell input, (x) -> x - 1
always_two = ComputeCell plus_one, minus_one, (x, y) -> x - y
callback = spy.new(->)

always_two\watch callback
input\set_value i for i = 1, 10
assert.spy(callback).was_called 0
Loading