Created
December 12, 2016 03:24
-
-
Save jimschubert/965984d141f7adca149619fe2ba565bd to your computer and use it in GitHub Desktop.
Try implementation in Kotlin that I've ditched but don't want to lose
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties | |
index 43ca251..9db3760 100644 | |
--- a/gradle/wrapper/gradle-wrapper.properties | |
+++ b/gradle/wrapper/gradle-wrapper.properties | |
@@ -1,6 +1,6 @@ | |
-#Mon Dec 05 21:26:08 EST 2016 | |
+#Sun Dec 11 10:08:31 EST 2016 | |
distributionBase=GRADLE_USER_HOME | |
distributionPath=wrapper/dists | |
zipStoreBase=GRADLE_USER_HOME | |
zipStorePath=wrapper/dists | |
-distributionUrl=https\://services.gradle.org/distributions/gradle-3.2-all.zip | |
+distributionUrl=https\://services.gradle.org/distributions/gradle-3.2-bin.zip | |
diff --git a/kopper/build.gradle b/kopper/build.gradle | |
index e6d71d4..7b701fe 100644 | |
--- a/kopper/build.gradle | |
+++ b/kopper/build.gradle | |
@@ -1,11 +1,6 @@ | |
dependencies { | |
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion" | |
- // Declare the dependency for your favourite test framework you want to use in your tests. | |
- // TestNG is also supported by the Gradle Test task. Just change the | |
- // testCompile dependency to testCompile 'org.testng:testng:6.8.1' and add | |
- // 'test.useTestNG()' to your build script. | |
- | |
testCompile 'org.testng:testng:6.8.1', | |
"org.jetbrains.kotlin:kotlin-test:$kotlinVersion" | |
} | |
diff --git a/kopper/src/main/kotlin/us/jimschubert/kopper/Try.kt b/kopper/src/main/kotlin/us/jimschubert/kopper/Try.kt | |
new file mode 100644 | |
index 0000000..01dfd65 | |
--- /dev/null | |
+++ b/kopper/src/main/kotlin/us/jimschubert/kopper/Try.kt | |
@@ -0,0 +1,116 @@ | |
+/** | |
+ * This code is based off of Scala's Try/Success/Failure interface. | |
+ * Scala is licensed to EPFL and Lightbend, Inc. | |
+ * see: https://github.com/scala/scala/blob/2.12.x/LICENSE | |
+ */ | |
+package us.jimschubert.kopper | |
+ | |
+import java.util.* | |
+ | |
+interface Try<T> { | |
+ fun isFailure(): Boolean | |
+ fun isSuccess(): Boolean | |
+ | |
+ fun get(): T | |
+ | |
+ fun <U : T> getOrElse(default: () -> U): T | |
+ fun <U : T> orElse(default: () -> Try<U>): Try<U> | |
+ | |
+ fun <U> foreach(f: (T) -> U): Unit | |
+ fun <U> flatMap(f: (T) -> Try<U>): Try<U> | |
+ fun <U> map(f: (T) -> U): Try<U> | |
+ // collect? Investigate equivalent of PartialFunction in kotlin. | |
+ | |
+ fun filter(predicate: (T) -> Boolean): Try<T> | |
+ | |
+ fun toOption(): T? | |
+ | |
+ fun <U> fold(whenFailure: (Throwable) -> U, whenSuccess: (T) -> U) : U | |
+ | |
+ // TODO: Recover | |
+ | |
+ companion object { | |
+ operator fun <T> invoke(value: () -> T): Try<T> = try { | |
+ Success(value.invoke()) | |
+ } catch (e:Exception) { | |
+ Failure(e) | |
+ } | |
+ } | |
+} | |
+ | |
+data class Failure<T>(val exception: Throwable) : Try<T> { | |
+ override fun isFailure(): Boolean = true | |
+ override fun isSuccess(): Boolean = false | |
+ override fun get(): T { | |
+ throw exception | |
+ } | |
+ | |
+ override fun <U : T> getOrElse(default: () -> U): T = default.invoke() | |
+ | |
+ override fun <U : T> orElse(default: () -> Try<U>): Try<U> { | |
+ return try { | |
+ default.invoke() | |
+ } catch (e:Exception) { | |
+ return Failure(e) | |
+ } | |
+ } | |
+ | |
+ override fun <U> foreach(f: (T) -> U) { } | |
+ | |
+ @Suppress("UNCHECKED_CAST") | |
+ override fun <U> flatMap(f: (T) -> Try<U>): Try<U> = Failure(exception) | |
+ | |
+ @Suppress("UNCHECKED_CAST") | |
+ override fun <U> map(f: (T) -> U): Try<U> = Failure(exception) | |
+ | |
+ override fun filter(predicate: (T) -> Boolean): Try<T> = this | |
+ | |
+ override fun toOption(): T? = null | |
+ | |
+ override fun <U> fold(whenFailure: (Throwable) -> U, whenSuccess: (T) -> U): U = whenFailure.invoke(exception) | |
+} | |
+ | |
+data class Success<T>(val value: T) : Try<T> { | |
+ override fun isFailure(): Boolean = false | |
+ override fun isSuccess(): Boolean = true | |
+ override fun get(): T = value | |
+ | |
+ @Suppress("UNCHECKED_CAST") | |
+ override fun <U : T> getOrElse(default: () -> U): T = get() | |
+ | |
+ @Suppress("UNCHECKED_CAST") | |
+ override fun <U : T> orElse(default: () -> Try<U>): Try<U> = this as Try<U> | |
+ | |
+ override fun <U> foreach(f: (T) -> U) { | |
+ f.invoke(value) | |
+ } | |
+ | |
+ override fun <U> flatMap(f: (T) -> Try<U>): Try<U> { | |
+ return try { | |
+ f.invoke(value) | |
+ } catch (e:Exception) { | |
+ return Failure(e) | |
+ } | |
+ } | |
+ | |
+ override fun <U> map(f: (T) -> U): Try<U> = Try { f.invoke(value) } | |
+ | |
+ override fun filter(predicate: (T) -> Boolean): Try<T> { | |
+ return try { | |
+ if(predicate.invoke(value)) this else Failure(NoSuchElementException("Predicate does not hold for $value")) | |
+ } catch (e:Exception) { | |
+ return Failure(e) | |
+ } | |
+ } | |
+ | |
+ override fun toOption(): T? = value | |
+ | |
+ override fun <U> fold(whenFailure: (Throwable) -> U, whenSuccess: (T) -> U): U { | |
+ return try { | |
+ whenSuccess(value) | |
+ } catch (e:Exception) { | |
+ return whenFailure(e) | |
+ } | |
+ } | |
+ | |
+} | |
\ No newline at end of file | |
diff --git a/kopper/src/test/kotlin/us/jimschubert/kopper/TryTest.kt b/kopper/src/test/kotlin/us/jimschubert/kopper/TryTest.kt | |
new file mode 100644 | |
index 0000000..ad58feb | |
--- /dev/null | |
+++ b/kopper/src/test/kotlin/us/jimschubert/kopper/TryTest.kt | |
@@ -0,0 +1,371 @@ | |
+package us.jimschubert.kopper | |
+ | |
+import org.testng.Assert.* | |
+import org.testng.annotations.Test | |
+import java.util.* | |
+ | |
+class MyInvalidValueException : Exception() | |
+class TestClass(var value: Int) | |
+class Result(val successful: Boolean) | |
+ | |
+class TryTest { | |
+ @Test | |
+ fun `Success instance for valid value`() { | |
+ // Arrange | |
+ val valid: () -> Boolean = { | |
+ true | |
+ } | |
+ | |
+ // Act | |
+ val tried = Try(valid) | |
+ | |
+ // Assert | |
+ assertTrue(tried is Try<Boolean>) | |
+ assertTrue(tried is Try<Boolean>) | |
+ assertTrue(tried.isSuccess()) | |
+ assertFalse(tried.isFailure()) | |
+ assertEquals(tried.get(), true) | |
+ } | |
+ | |
+ @Test(expectedExceptions = arrayOf(MyInvalidValueException::class)) | |
+ fun `Failure instance for thrown exception`() { | |
+ // Arrange | |
+ val invalid: () -> Boolean = { | |
+ throw MyInvalidValueException() | |
+ } | |
+ | |
+ // Act | |
+ val tried = Try(invalid) | |
+ | |
+ // Assert | |
+ assertTrue(tried is Try<Boolean>) | |
+ assertTrue(tried is Try<Boolean>) | |
+ assertFalse(tried.isSuccess()) | |
+ assertTrue(tried.isFailure()) | |
+ | |
+ tried.get() // should throw | |
+ } | |
+ | |
+ @Test | |
+ fun `Success getOrElse`() { | |
+ // Arrange | |
+ val value = 3 | |
+ val success = Success(value) | |
+ | |
+ // Act | |
+ val result = success.getOrElse { 4 } | |
+ | |
+ // Assert | |
+ assertEquals(result, value) | |
+ } | |
+ | |
+ @Test | |
+ fun `Failure getOrElse`() { | |
+ // Arrange | |
+ val value = 3 | |
+ val failure = Failure<Int>(MyInvalidValueException()) | |
+ | |
+ // Act | |
+ val result = failure.getOrElse { value } | |
+ | |
+ // Assert | |
+ assertEquals(result, value) | |
+ } | |
+ | |
+ @Test | |
+ fun `Success orElse`() { | |
+ // Arrange | |
+ val value = 3 | |
+ val success = Success(value) | |
+ | |
+ // Act | |
+ val result: Try<Int> = success.orElse { Success(4) } | |
+ | |
+ // Assert | |
+ assertTrue(result.isSuccess()) | |
+ assertEquals(result.get(), value) | |
+ } | |
+ | |
+ @Test | |
+ fun `Failure orElse`() { | |
+ // Arrange | |
+ val value = 3 | |
+ val failure = Failure<Int>(MyInvalidValueException()) | |
+ | |
+ // Act | |
+ val result = failure.orElse { Success(value) } | |
+ | |
+ // Assert | |
+ assertTrue(result.isSuccess()) | |
+ assertEquals(result.get(), value) | |
+ } | |
+ | |
+ @Test | |
+ fun `Success foreach`() { | |
+ // Arrange | |
+ val value = 3 | |
+ val expected = 4 | |
+ val accumulator = TestClass(value) | |
+ val success = Success(accumulator) | |
+ | |
+ // Act | |
+ success.foreach { acc -> | |
+ acc.value = acc.value + 1 | |
+ } | |
+ | |
+ // Assert | |
+ assertTrue(success.isSuccess()) | |
+ assertEquals(accumulator.value, expected) | |
+ } | |
+ | |
+ @Test | |
+ fun `Failure foreach`() { | |
+ // Arrange | |
+ val value = 3 | |
+ val expected = 3 | |
+ val accumulator = TestClass(value) | |
+ val failure = Failure<TestClass>(MyInvalidValueException()) | |
+ | |
+ // Act | |
+ failure.foreach { acc -> | |
+ acc.value = acc.value + 1 | |
+ } | |
+ | |
+ // Assert | |
+ assertTrue(failure.isFailure()) | |
+ assertEquals(accumulator.value, expected) | |
+ } | |
+ | |
+ @Test | |
+ fun `Success flatMap to Success`() { | |
+ // Arrange | |
+ val value = 3 | |
+ val expected = 4 | |
+ val accumulator = TestClass(value) | |
+ val success = Success(accumulator) | |
+ | |
+ // Act | |
+ val result: Try<Int> = success.flatMap { acc -> | |
+ Success(acc.value + 1) | |
+ } | |
+ | |
+ // Assert | |
+ assertTrue(result.isSuccess()) | |
+ assertEquals(result.get(), expected) | |
+ } | |
+ | |
+ @Test(expectedExceptions = arrayOf(MyInvalidValueException::class)) | |
+ fun `Success flatMap to Failure`() { | |
+ // Arrange | |
+ val value = 3 | |
+ val accumulator = TestClass(value) | |
+ val success = Success(accumulator) | |
+ | |
+ // Act | |
+ val result: Try<Int> = success.flatMap { acc -> | |
+ Failure<Int>(MyInvalidValueException()) | |
+ } | |
+ | |
+ // Assert | |
+ assertTrue(result.isFailure()) | |
+ result.get() // should throw | |
+ } | |
+ | |
+ @Test(expectedExceptions = arrayOf(MyInvalidValueException::class)) | |
+ fun `Failure flatMap`() { | |
+ // Arrange | |
+ val failure = Failure<TestClass>(MyInvalidValueException()) | |
+ | |
+ // Act | |
+ val result: Try<Int> = failure.flatMap { acc -> | |
+ Success(acc.value + 1) | |
+ } | |
+ | |
+ // Assert | |
+ assertTrue(result.isFailure()) | |
+ result.get() // should throw | |
+ } | |
+ | |
+ @Test | |
+ fun `Success map`() { | |
+ // Arrange | |
+ val value = 3 | |
+ val expected = 4 | |
+ val accumulator = TestClass(value) | |
+ val success = Success(accumulator) | |
+ | |
+ // Act | |
+ val result: Try<Int> = success.map { acc -> | |
+ acc.value + 1 | |
+ } | |
+ | |
+ // Assert | |
+ assertTrue(result.isSuccess()) | |
+ assertEquals(result.get(), expected) | |
+ } | |
+ | |
+ @Test(expectedExceptions = arrayOf(MyInvalidValueException::class)) | |
+ fun `Failure map`() { | |
+ // Arrange | |
+ val failure = Failure<TestClass>(MyInvalidValueException()) | |
+ | |
+ // Act | |
+ val result: Try<Int> = failure.map { acc -> | |
+ acc.value + 1 | |
+ } | |
+ | |
+ // Assert | |
+ assertTrue(result.isFailure()) | |
+ result.get() // should throw | |
+ } | |
+ | |
+ @Test | |
+ fun `Success filter`() { | |
+ // Arrange | |
+ val value = 3 | |
+ val tester = TestClass(value) | |
+ val success = Success(tester) | |
+ | |
+ // Act | |
+ val result: Try<TestClass> = success.filter { t -> | |
+ t.value == 3 | |
+ } | |
+ | |
+ // Assert | |
+ assertTrue(result.isSuccess()) | |
+ assertEquals(result.get().value, value) | |
+ } | |
+ | |
+ @Test(expectedExceptions = arrayOf(NoSuchElementException::class)) | |
+ fun `Success filter fails`() { | |
+ // Arrange | |
+ val value = 3 | |
+ val expected = 4 | |
+ val tester = TestClass(value) | |
+ val success = Success(tester) | |
+ | |
+ // Act | |
+ val result: Try<TestClass> = success.filter { t -> | |
+ t.value == 100 | |
+ } | |
+ | |
+ // Assert | |
+ assertTrue(result.isFailure()) | |
+ assertEquals(result.get(), expected) | |
+ } | |
+ | |
+ @Test(expectedExceptions = arrayOf(NotImplementedError::class)) | |
+ fun `Success filter exception`() { | |
+ // Arrange | |
+ val value = 3 | |
+ val expected = 4 | |
+ val tester = TestClass(value) | |
+ val success = Success(tester) | |
+ | |
+ // Act | |
+ val result: Try<TestClass> = success.filter { t -> | |
+ throw NotImplementedError() | |
+ } | |
+ | |
+ // Assert | |
+ assertTrue(result.isFailure()) | |
+ assertEquals(result.get(), expected) | |
+ } | |
+ | |
+ @Test(expectedExceptions = arrayOf(MyInvalidValueException::class)) | |
+ fun `Failure filter`() { | |
+ // Arrange | |
+ val failure = Failure<TestClass>(MyInvalidValueException()) | |
+ | |
+ // Act | |
+ val result: Try<TestClass> = failure.filter { acc -> | |
+ acc.value == 3 | |
+ } | |
+ | |
+ // Assert | |
+ assertTrue(result.isFailure()) | |
+ result.get() // should throw | |
+ } | |
+ | |
+ @Test | |
+ fun `Success toOption`() { | |
+ // Arrange | |
+ val value = 3 | |
+ val tester = TestClass(value) | |
+ val success = Success(tester) | |
+ | |
+ // Act | |
+ val result: TestClass? = success.toOption() | |
+ | |
+ // Assert | |
+ assertEquals(result?.value, value) | |
+ } | |
+ | |
+ @Test | |
+ fun `Failure toOption`() { | |
+ // Arrange | |
+ val failure = Failure<TestClass>(MyInvalidValueException()) | |
+ | |
+ // Act | |
+ val result: TestClass? = failure.toOption() | |
+ | |
+ // Assert | |
+ assertNull(result) | |
+ } | |
+ | |
+ @Test | |
+ fun `Success fold success`() { | |
+ // Arrange | |
+ val value = 3 | |
+ val tester = TestClass(value) | |
+ val success = Success(tester) | |
+ | |
+ // Act | |
+ val result: Result = success.fold({ throwable -> | |
+ Result(false) | |
+ }) { successful -> | |
+ Result(true) | |
+ } | |
+ | |
+ // Assert | |
+ assertTrue(result.successful) | |
+ } | |
+ | |
+ @Test | |
+ fun `Success fold exception`() { | |
+ // Arrange | |
+ val value = 3 | |
+ val tester = TestClass(value) | |
+ val success = Success(tester) | |
+ | |
+ // Act | |
+ val result: Result = success.fold({ throwable -> | |
+ Result(false) | |
+ }) { successful -> | |
+ throw MyInvalidValueException() | |
+ } | |
+ | |
+ // Assert | |
+ assertFalse(result.successful) | |
+ } | |
+ | |
+ | |
+ @Test | |
+ fun `Failure fold err`() { | |
+ // Arrange | |
+ val value = 3 | |
+ val tester = TestClass(value) | |
+ val success = Success(tester) | |
+ val failure = Failure<TestClass>(MyInvalidValueException()) | |
+ | |
+ // Act | |
+ val result: Result = (success.flatMap { failure }).fold({ throwable -> | |
+ Result(false) | |
+ }) { successful -> | |
+ Result(true) | |
+ } | |
+ | |
+ // Assert | |
+ assertFalse(result.successful) | |
+ } | |
+} | |
\ No newline at end of file |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment