-
-
Notifications
You must be signed in to change notification settings - Fork 203
react: add to track #29
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
60adbe3
b60b45b
e356e40
c2b907e
cc174c5
75ccd07
d5fcdcc
aeb4ea8
75f14fa
58f3b19
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
buildscript { | ||
ext.kotlin_version = '1.0.6' | ||
repositories { | ||
mavenCentral() | ||
} | ||
dependencies { | ||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" | ||
} | ||
} | ||
|
||
apply plugin: 'kotlin' | ||
|
||
repositories { | ||
mavenCentral() | ||
} | ||
|
||
dependencies { | ||
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" | ||
|
||
testCompile 'junit:junit:4.12' | ||
testCompile "org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version" | ||
} | ||
test { | ||
testLogging { | ||
exceptionFormat = 'full' | ||
events = ["passed", "failed", "skipped"] | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
class Reactor<T>() { | ||
abstract inner class Cell { | ||
abstract val value: T | ||
internal val dependents = mutableListOf<ComputeCell>() | ||
} | ||
|
||
interface Subscription { | ||
fun cancel() | ||
} | ||
|
||
inner class InputCell(initialValue: T) : Cell() { | ||
override var value: T = initialValue | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. looks like http://kotlinlang.org/docs/reference/delegated-properties.html#observable could be used here, I'll consider it, though it's understandable enough without it. (I'll probably not use it in the PR. Maybe in my submission to exercism.io!) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, observables do seem like a great fit for this exercise - I wonder if there is a way to leverage the ObservableProperty as a super type. But what you currently have works, we should care more about if API driven by the tests make sense. |
||
set(newValue) { | ||
field = newValue | ||
dependents.forEach { it.propagate() } | ||
dependents.forEach { it.fireCallbacks() } | ||
} | ||
} | ||
|
||
inner class ComputeCell private constructor(val newValue: () -> T) : Cell() { | ||
override var value: T = newValue() | ||
private set | ||
|
||
private var lastCallbackValue = value | ||
private var callbacksIssued = 0 | ||
private val activeCallbacks = mutableMapOf<Int, (T) -> Any>() | ||
|
||
constructor(vararg cells: Cell, f: (List<T>) -> T) : this({ f(cells.map { it.value }) }) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
for (cell in cells) { | ||
cell.dependents.add(this) | ||
} | ||
} | ||
|
||
fun addCallback(f: (T) -> Any): Subscription { | ||
val id = callbacksIssued | ||
callbacksIssued++ | ||
activeCallbacks[id] = f | ||
return object : Subscription { | ||
override fun cancel() { | ||
activeCallbacks.remove(id) | ||
} | ||
} | ||
} | ||
|
||
internal fun propagate() { | ||
val nv = newValue() | ||
if (nv == value) { | ||
return | ||
} | ||
value = nv | ||
dependents.forEach { it.propagate() } | ||
} | ||
|
||
internal fun fireCallbacks() { | ||
if (value == lastCallbackValue) { | ||
return | ||
} | ||
lastCallbackValue = value | ||
for (cb in activeCallbacks.values) { | ||
cb(value) | ||
} | ||
dependents.forEach { it.fireCallbacks() } | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
class Reactor<T>() { | ||
// Your compute cell's addCallback method must return an object | ||
// that implements the Subscription interface. | ||
interface Subscription { | ||
fun cancel() | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,221 @@ | ||
import org.junit.Test | ||
import org.junit.Ignore | ||
import org.junit.runner.RunWith | ||
import org.junit.runners.Parameterized | ||
import kotlin.test.assertEquals | ||
|
||
class ReactTest { | ||
@Test | ||
fun inputCellsHaveValue() { | ||
val reactor = Reactor<Int>() | ||
val input = reactor.InputCell(10) | ||
assertEquals(10, input.value) | ||
} | ||
|
||
@Ignore | ||
@Test | ||
fun inputCellsValueCanBeSet() { | ||
val reactor = Reactor<Int>() | ||
val input = reactor.InputCell(4) | ||
input.value = 20 | ||
assertEquals(20, input.value) | ||
} | ||
|
||
@Ignore | ||
@Test | ||
fun computeCellsCalculateInitialValue() { | ||
val reactor = Reactor<Int>() | ||
val input = reactor.InputCell(1) | ||
val output = reactor.ComputeCell(input) { it[0] + 1 } | ||
assertEquals(2, output.value) | ||
} | ||
|
||
@Ignore | ||
@Test | ||
fun computeCellsTakeInputsInTheRightOrder() { | ||
val reactor = Reactor<Int>() | ||
val one = reactor.InputCell(1) | ||
val two = reactor.InputCell(2) | ||
val output = reactor.ComputeCell(one, two) { it[0] + it[1] * 10 } | ||
assertEquals(21, output.value) | ||
} | ||
|
||
@Ignore | ||
@Test | ||
fun computeCellsUpdateValueWhenDependenciesAreChanged() { | ||
val reactor = Reactor<Int>() | ||
val input = reactor.InputCell(1) | ||
val output = reactor.ComputeCell(input) { it[0] + 1 } | ||
input.value = 3 | ||
assertEquals(4, output.value) | ||
} | ||
|
||
@Ignore | ||
@Test | ||
fun computeCellsCanDependOnOtherComputeCells() { | ||
val reactor = Reactor<Int>() | ||
val input = reactor.InputCell(1) | ||
val timesTwo = reactor.ComputeCell(input) { it[0] * 2 } | ||
val timesThirty = reactor.ComputeCell(input) { it[0] * 30 } | ||
val output = reactor.ComputeCell(timesTwo, timesThirty) { it[0] + it[1] } | ||
|
||
assertEquals(32, output.value) | ||
input.value = 3 | ||
assertEquals(96, output.value) | ||
} | ||
|
||
@Ignore | ||
@Test | ||
fun computeCellsFireCallbacks() { | ||
val reactor = Reactor<Int>() | ||
val input = reactor.InputCell(1) | ||
val output = reactor.ComputeCell(input) { it[0] + 1 } | ||
|
||
val vals = mutableListOf<Int>() | ||
output.addCallback { vals.add(it) } | ||
|
||
input.value = 3 | ||
assertEquals(listOf(4), vals) | ||
} | ||
|
||
@Ignore | ||
@Test | ||
fun callbacksOnlyFireOnChange() { | ||
val reactor = Reactor<Int>() | ||
val input = reactor.InputCell(1) | ||
val output = reactor.ComputeCell(input) { if (it[0] < 3) 111 else 222 } | ||
|
||
val vals = mutableListOf<Int>() | ||
output.addCallback { vals.add(it) } | ||
|
||
input.value = 2 | ||
assertEquals(listOf<Int>(), vals) | ||
|
||
input.value = 4 | ||
assertEquals(listOf(222), vals) | ||
} | ||
|
||
@Ignore | ||
@Test | ||
fun callbacksCanBeAddedAndRemoved() { | ||
val reactor = Reactor<Int>() | ||
val input = reactor.InputCell(11) | ||
val output = reactor.ComputeCell(input) { it[0] + 1 } | ||
|
||
val vals1 = mutableListOf<Int>() | ||
val sub1 = output.addCallback { vals1.add(it) } | ||
val vals2 = mutableListOf<Int>() | ||
output.addCallback { vals2.add(it) } | ||
|
||
input.value = 31 | ||
sub1.cancel() | ||
|
||
val vals3 = mutableListOf<Int>() | ||
output.addCallback { vals3.add(it) } | ||
|
||
input.value = 41 | ||
|
||
assertEquals(listOf(32), vals1) | ||
assertEquals(listOf(32, 42), vals2) | ||
assertEquals(listOf(42), vals3) | ||
} | ||
|
||
@Ignore | ||
@Test | ||
fun removingACallbackMultipleTimesDoesntInterfereWithOtherCallbacks() { | ||
val reactor = Reactor<Int>() | ||
val input = reactor.InputCell(1) | ||
val output = reactor.ComputeCell(input) { it[0] + 1 } | ||
|
||
val vals1 = mutableListOf<Int>() | ||
val sub1 = output.addCallback { vals1.add(it) } | ||
val vals2 = mutableListOf<Int>() | ||
output.addCallback { vals2.add(it) } | ||
|
||
for (i in 1..10) { | ||
sub1.cancel() | ||
} | ||
|
||
input.value = 2 | ||
assertEquals(listOf<Int>(), vals1) | ||
assertEquals(listOf(3), vals2) | ||
} | ||
|
||
@Ignore | ||
@Test | ||
fun callbacksShouldOnlyBeCalledOnceEvenIfMultipleDependenciesChange() { | ||
val reactor = Reactor<Int>() | ||
val input = reactor.InputCell(1) | ||
val plusOne = reactor.ComputeCell(input) { it[0] + 1 } | ||
val minusOne1 = reactor.ComputeCell(input) { it[0] - 1 } | ||
val minusOne2 = reactor.ComputeCell(minusOne1) { it[0] - 1 } | ||
val output = reactor.ComputeCell(plusOne, minusOne2) { it[0] * it[1] } | ||
|
||
val vals = mutableListOf<Int>() | ||
output.addCallback { vals.add(it) } | ||
|
||
input.value = 4 | ||
assertEquals(listOf(10), vals) | ||
} | ||
|
||
@Ignore | ||
@Test | ||
fun callbacksShouldNotBeCalledIfDependenciesChangeButOutputValueDoesntChange() { | ||
val reactor = Reactor<Int>() | ||
val input = reactor.InputCell(1) | ||
val plusOne = reactor.ComputeCell(input) { it[0] + 1 } | ||
val minusOne = reactor.ComputeCell(input) { it[0] - 1 } | ||
val alwaysTwo = reactor.ComputeCell(plusOne, minusOne) { it[0] - it[1] } | ||
|
||
val vals = mutableListOf<Int>() | ||
alwaysTwo.addCallback { vals.add(it) } | ||
|
||
for (i in 1..10) { | ||
input.value = i | ||
} | ||
|
||
assertEquals(listOf<Int>(), vals) | ||
} | ||
} | ||
|
||
@RunWith(Parameterized::class) | ||
// This is a digital logic circuit called an adder: | ||
// https://en.wikipedia.org/wiki/Adder_(electronics) | ||
class ReactAdderTest(val input: Input, val expected: Expected) { | ||
|
||
companion object { | ||
data class Input(val a: Boolean, val b: Boolean, val carryIn: Boolean) | ||
data class Expected(val carryOut: Boolean, val sum: Boolean) | ||
|
||
@JvmStatic | ||
@Parameterized.Parameters(name = "{index}: {0} = {1}") | ||
fun data() = listOf( | ||
arrayOf(Input(a=false, b=false, carryIn=false), Expected(carryOut=false, sum=false)), | ||
arrayOf(Input(a=false, b=false, carryIn=true), Expected(carryOut=false, sum=true)), | ||
arrayOf(Input(a=false, b=true, carryIn=false), Expected(carryOut=false, sum=true)), | ||
arrayOf(Input(a=false, b=true, carryIn=true), Expected(carryOut=true, sum=false)), | ||
arrayOf(Input(a=true, b=false, carryIn=false), Expected(carryOut=false, sum=true)), | ||
arrayOf(Input(a=true, b=false, carryIn=true), Expected(carryOut=true, sum=false)), | ||
arrayOf(Input(a=true, b=true, carryIn=false), Expected(carryOut=true, sum=false)), | ||
arrayOf(Input(a=true, b=true, carryIn=true), Expected(carryOut=true, sum=true)) | ||
) | ||
} | ||
|
||
@Ignore | ||
@Test | ||
fun test() { | ||
val reactor = Reactor<Boolean>() | ||
val a = reactor.InputCell(input.a) | ||
val b = reactor.InputCell(input.b) | ||
val carryIn = reactor.InputCell(input.carryIn) | ||
|
||
val aXorB = reactor.ComputeCell(a, b) { it[0].xor(it[1]) } | ||
val sum = reactor.ComputeCell(aXorB, carryIn) { it[0].xor(it[1]) } | ||
|
||
val aXorBAndCin = reactor.ComputeCell(aXorB, carryIn) { it[0] && it[1] } | ||
val aAndB = reactor.ComputeCell(a, b) { it[0] && it[1] } | ||
val carryOut = reactor.ComputeCell(aXorBAndCin, aAndB) { it[0] || it[1] } | ||
|
||
assertEquals(expected, Expected(sum=sum.value, carryOut=carryOut.value)) | ||
} | ||
} |
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
interestingly,
protected
is not possible, because then thec.dependents.add(this)
in the constructors all fail compilation with:This is a little strange to me, since
ComputeCell
is (I hope?!) a subclass ofCell
. Perhaps it doesn't apply inside constructors, or something like that... after all, you could consider the constructor to be a function not associated with any particular class, but it does return an instance of a particular class...?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yea, I'm likewise surprised that this error shows up for a
protected
variable when being accessed in a subclass' constructor, it is available in the subclass outside of the constructor though...