From 2e83c3f8e43d7de59c00c542281d49b3e419e827 Mon Sep 17 00:00:00 2001 From: Arturo Gutierrez Date: Fri, 24 Mar 2017 21:59:35 +0100 Subject: [PATCH 1/8] State skeleton --- katz/src/main/kotlin/katz/State.kt | 31 ++++++++++++++++++++++++ katz/src/test/kotlin/katz/StateTests.kt | 32 +++++++++++++++++++++++++ 2 files changed, 63 insertions(+) create mode 100644 katz/src/main/kotlin/katz/State.kt create mode 100644 katz/src/test/kotlin/katz/StateTests.kt diff --git a/katz/src/main/kotlin/katz/State.kt b/katz/src/main/kotlin/katz/State.kt new file mode 100644 index 00000000000..7894f2329e1 --- /dev/null +++ b/katz/src/main/kotlin/katz/State.kt @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2017 The Katz Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package katz + +/** + * State[S, A] is basically a function S => (S, A), + * where S is the type that represents your state + * and A is the result the function produces. In addition + * to returning the result of type A, the function + * returns a new S value, which is the updated state. + */ +class State(val runF: (S) -> Pair) { + + fun run(initial: S): Pair { + return runF(initial) + } +} diff --git a/katz/src/test/kotlin/katz/StateTests.kt b/katz/src/test/kotlin/katz/StateTests.kt new file mode 100644 index 00000000000..917d3196f37 --- /dev/null +++ b/katz/src/test/kotlin/katz/StateTests.kt @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2017 The Katz Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package katz + +import io.kotlintest.KTestJUnitRunner +import org.junit.runner.RunWith + +@RunWith(KTestJUnitRunner::class) +class StateTests : UnitSpec() { + + private val addOne = State { n -> Pair(n + 1, n) } + + init { + "addOne.run 1 " should "return a Pair(2, 1)" { + addOne.run(1) shouldBe Pair(2, 1) + } + } +} From a747e0e2e09d1ccb022aeb998d635bd323d26ea9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arturo=20Guti=C3=A9rrez?= Date: Fri, 24 Mar 2017 23:54:58 +0100 Subject: [PATCH 2/8] State.map added --- katz/src/main/kotlin/katz/State.kt | 6 ++++++ katz/src/test/kotlin/katz/StateTests.kt | 10 +++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/katz/src/main/kotlin/katz/State.kt b/katz/src/main/kotlin/katz/State.kt index 7894f2329e1..313c1d8e91f 100644 --- a/katz/src/main/kotlin/katz/State.kt +++ b/katz/src/main/kotlin/katz/State.kt @@ -28,4 +28,10 @@ class State(val runF: (S) -> Pair) { fun run(initial: S): Pair { return runF(initial) } + + fun map(f: (A) -> B): State { + return State { s1 -> + run(s1).let { (s2, a2) -> Pair(s2, f(a2)) } + } + } } diff --git a/katz/src/test/kotlin/katz/StateTests.kt b/katz/src/test/kotlin/katz/StateTests.kt index 917d3196f37..a7069af3e6c 100644 --- a/katz/src/test/kotlin/katz/StateTests.kt +++ b/katz/src/test/kotlin/katz/StateTests.kt @@ -25,8 +25,16 @@ class StateTests : UnitSpec() { private val addOne = State { n -> Pair(n + 1, n) } init { - "addOne.run 1 " should "return a Pair(2, 1)" { + "addOne.run 1 " should "return Pair(2, 1)" { addOne.run(1) shouldBe Pair(2, 1) } + + "addOne.map n -> n .run 1" should "return same Pair(2, 1)" { + addOne.map { n -> n }.run(1) shouldBe Pair(2, 1) + } + + "addOne.map n -> n.toString .run 1" should "return same Pair(2, \"1\")" { + addOne.map(Int::toString).run(1) shouldBe Pair(2, "1") + } } } From 5859d3b57779e643f91ba0600481fcf02177c3c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arturo=20Guti=C3=A9rrez?= Date: Sat, 25 Mar 2017 00:28:41 +0100 Subject: [PATCH 3/8] State.flatMap and State.eval added --- katz/src/main/kotlin/katz/State.kt | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/katz/src/main/kotlin/katz/State.kt b/katz/src/main/kotlin/katz/State.kt index 313c1d8e91f..9c3a0cf93a9 100644 --- a/katz/src/main/kotlin/katz/State.kt +++ b/katz/src/main/kotlin/katz/State.kt @@ -25,8 +25,8 @@ package katz */ class State(val runF: (S) -> Pair) { - fun run(initial: S): Pair { - return runF(initial) + fun run(s: S): Pair { + return runF(s) } fun map(f: (A) -> B): State { @@ -34,4 +34,12 @@ class State(val runF: (S) -> Pair) { run(s1).let { (s2, a2) -> Pair(s2, f(a2)) } } } + + fun flatMap(f: (A) -> State): State { + return State { s1 -> + run(s1).let { (s2, a2) -> f(a2).run(s2) } + } + } + + fun eval(s: S): A = run(s).second } From 9fe70b99976eb4f1b110db0e3e445ec01d35eaf4 Mon Sep 17 00:00:00 2001 From: Arturo Gutierrez Date: Fri, 31 Mar 2017 09:33:13 +0200 Subject: [PATCH 4/8] Adding runA and runS. Tests included. --- katz/src/main/kotlin/katz/{ => data}/State.kt | 7 +++++-- .../test/kotlin/katz/{ => data}/StateTests.kt | 19 +++++++++++++++---- 2 files changed, 20 insertions(+), 6 deletions(-) rename katz/src/main/kotlin/katz/{ => data}/State.kt (93%) rename katz/src/test/kotlin/katz/{ => data}/StateTests.kt (68%) diff --git a/katz/src/main/kotlin/katz/State.kt b/katz/src/main/kotlin/katz/data/State.kt similarity index 93% rename from katz/src/main/kotlin/katz/State.kt rename to katz/src/main/kotlin/katz/data/State.kt index 9c3a0cf93a9..b2d0e1a9b54 100644 --- a/katz/src/main/kotlin/katz/State.kt +++ b/katz/src/main/kotlin/katz/data/State.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package katz +package katz.data /** * State[S, A] is basically a function S => (S, A), @@ -29,6 +29,10 @@ class State(val runF: (S) -> Pair) { return runF(s) } + fun runA(s: S): A = run(s).second + + fun runS(s: S): S = run(s).first + fun map(f: (A) -> B): State { return State { s1 -> run(s1).let { (s2, a2) -> Pair(s2, f(a2)) } @@ -41,5 +45,4 @@ class State(val runF: (S) -> Pair) { } } - fun eval(s: S): A = run(s).second } diff --git a/katz/src/test/kotlin/katz/StateTests.kt b/katz/src/test/kotlin/katz/data/StateTests.kt similarity index 68% rename from katz/src/test/kotlin/katz/StateTests.kt rename to katz/src/test/kotlin/katz/data/StateTests.kt index a7069af3e6c..45da67f5b57 100644 --- a/katz/src/test/kotlin/katz/StateTests.kt +++ b/katz/src/test/kotlin/katz/data/StateTests.kt @@ -14,9 +14,12 @@ * limitations under the License. */ -package katz +package katz.data import io.kotlintest.KTestJUnitRunner +import io.kotlintest.matchers.shouldBe +import katz.UnitSpec +import katz.data.State import org.junit.runner.RunWith @RunWith(KTestJUnitRunner::class) @@ -25,16 +28,24 @@ class StateTests : UnitSpec() { private val addOne = State { n -> Pair(n + 1, n) } init { - "addOne.run 1 " should "return Pair(2, 1)" { + "addOne.run(1) should return Pair(2, 1)" { addOne.run(1) shouldBe Pair(2, 1) } - "addOne.map n -> n .run 1" should "return same Pair(2, 1)" { + "addOne.map(n -> n).run(1) should return same Pair(2, 1)" { addOne.map { n -> n }.run(1) shouldBe Pair(2, 1) } - "addOne.map n -> n.toString .run 1" should "return same Pair(2, \"1\")" { + "addOne.map(n -> n.toString).run(1) should return same Pair(2, \"1\")" { addOne.map(Int::toString).run(1) shouldBe Pair(2, "1") } + + "addOne.runS(1) should return 2" { + addOne.runS(1) shouldBe 2 + } + + "addOne.runA(1) should return 1" { + addOne.runA(1) shouldBe 1 + } } } From 6461f1a0935fbd4df712ba6932ac207e56716aa3 Mon Sep 17 00:00:00 2001 From: Arturo Gutierrez Date: Fri, 31 Mar 2017 09:36:51 +0200 Subject: [PATCH 5/8] Moving State to katz root package --- katz/src/main/kotlin/katz/data/State.kt | 2 +- katz/src/test/kotlin/katz/data/StateTests.kt | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/katz/src/main/kotlin/katz/data/State.kt b/katz/src/main/kotlin/katz/data/State.kt index b2d0e1a9b54..e4bcb617193 100644 --- a/katz/src/main/kotlin/katz/data/State.kt +++ b/katz/src/main/kotlin/katz/data/State.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package katz.data +package katz /** * State[S, A] is basically a function S => (S, A), diff --git a/katz/src/test/kotlin/katz/data/StateTests.kt b/katz/src/test/kotlin/katz/data/StateTests.kt index 45da67f5b57..7bc198a5e7a 100644 --- a/katz/src/test/kotlin/katz/data/StateTests.kt +++ b/katz/src/test/kotlin/katz/data/StateTests.kt @@ -14,12 +14,10 @@ * limitations under the License. */ -package katz.data +package katz import io.kotlintest.KTestJUnitRunner import io.kotlintest.matchers.shouldBe -import katz.UnitSpec -import katz.data.State import org.junit.runner.RunWith @RunWith(KTestJUnitRunner::class) From e907356ab9968a4e1c337d7b3756cfc32d8db4ba Mon Sep 17 00:00:00 2001 From: Arturo Gutierrez Date: Fri, 31 Mar 2017 11:22:34 +0200 Subject: [PATCH 6/8] Using Tuple2 instead of Pair --- katz/src/main/kotlin/katz/data/State.kt | 10 +++++----- katz/src/test/kotlin/katz/data/StateTests.kt | 8 ++++---- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/katz/src/main/kotlin/katz/data/State.kt b/katz/src/main/kotlin/katz/data/State.kt index e4bcb617193..b524ae9cf2d 100644 --- a/katz/src/main/kotlin/katz/data/State.kt +++ b/katz/src/main/kotlin/katz/data/State.kt @@ -23,19 +23,19 @@ package katz * to returning the result of type A, the function * returns a new S value, which is the updated state. */ -class State(val runF: (S) -> Pair) { +class State(val runF: (S) -> Tuple2) { - fun run(s: S): Pair { + fun run(s: S): Tuple2 { return runF(s) } - fun runA(s: S): A = run(s).second + fun runA(s: S): A = run(s).b - fun runS(s: S): S = run(s).first + fun runS(s: S): S = run(s).a fun map(f: (A) -> B): State { return State { s1 -> - run(s1).let { (s2, a2) -> Pair(s2, f(a2)) } + run(s1).let { (s2, a2) -> Tuple2(s2, f(a2)) } } } diff --git a/katz/src/test/kotlin/katz/data/StateTests.kt b/katz/src/test/kotlin/katz/data/StateTests.kt index 7bc198a5e7a..1ced04f2196 100644 --- a/katz/src/test/kotlin/katz/data/StateTests.kt +++ b/katz/src/test/kotlin/katz/data/StateTests.kt @@ -23,19 +23,19 @@ import org.junit.runner.RunWith @RunWith(KTestJUnitRunner::class) class StateTests : UnitSpec() { - private val addOne = State { n -> Pair(n + 1, n) } + private val addOne = State { n -> Tuple2(n + 1, n) } init { "addOne.run(1) should return Pair(2, 1)" { - addOne.run(1) shouldBe Pair(2, 1) + addOne.run(1) shouldBe Tuple2(2, 1) } "addOne.map(n -> n).run(1) should return same Pair(2, 1)" { - addOne.map { n -> n }.run(1) shouldBe Pair(2, 1) + addOne.map { n -> n }.run(1) shouldBe Tuple2(2, 1) } "addOne.map(n -> n.toString).run(1) should return same Pair(2, \"1\")" { - addOne.map(Int::toString).run(1) shouldBe Pair(2, "1") + addOne.map(Int::toString).run(1) shouldBe Tuple2(2, "1") } "addOne.runS(1) should return 2" { From 677ac3fed8588cc5674c5f86f720694bc74367dc Mon Sep 17 00:00:00 2001 From: Fernando Franco Giraldez Date: Thu, 8 Jun 2017 10:50:16 +0200 Subject: [PATCH 7/8] Implement StateT and replace old State as alias of StateT --- katz/src/main/kotlin/katz/data/State.kt | 50 ++------------- katz/src/main/kotlin/katz/data/StateT.kt | 67 ++++++++++++++++++++ katz/src/test/kotlin/katz/data/StateTests.kt | 12 ++-- 3 files changed, 77 insertions(+), 52 deletions(-) create mode 100644 katz/src/main/kotlin/katz/data/StateT.kt diff --git a/katz/src/main/kotlin/katz/data/State.kt b/katz/src/main/kotlin/katz/data/State.kt index b524ae9cf2d..a575a4f81fd 100644 --- a/katz/src/main/kotlin/katz/data/State.kt +++ b/katz/src/main/kotlin/katz/data/State.kt @@ -1,48 +1,6 @@ -/* - * Copyright (C) 2017 The Katz Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - package katz -/** - * State[S, A] is basically a function S => (S, A), - * where S is the type that represents your state - * and A is the result the function produces. In addition - * to returning the result of type A, the function - * returns a new S value, which is the updated state. - */ -class State(val runF: (S) -> Tuple2) { - - fun run(s: S): Tuple2 { - return runF(s) - } - - fun runA(s: S): A = run(s).b - - fun runS(s: S): S = run(s).a - - fun map(f: (A) -> B): State { - return State { s1 -> - run(s1).let { (s2, a2) -> Tuple2(s2, f(a2)) } - } - } - - fun flatMap(f: (A) -> State): State { - return State { s1 -> - run(s1).let { (s2, a2) -> f(a2).run(s2) } - } - } - -} +object State { + operator fun invoke(run: (S) -> Tuple2, MF: Monad = Id): StateT = + StateT(MF, Id(run.andThen { Id(it) })) +} \ No newline at end of file diff --git a/katz/src/main/kotlin/katz/data/StateT.kt b/katz/src/main/kotlin/katz/data/StateT.kt new file mode 100644 index 00000000000..d11e0475422 --- /dev/null +++ b/katz/src/main/kotlin/katz/data/StateT.kt @@ -0,0 +1,67 @@ +package katz + +typealias StateTKind = HK3 + +typealias StateTFun = (S) -> HK> +typealias StateTFunKind = HK> + +fun StateTKind.ev(): StateT = + this as StateT + +class StateT( + val MF: Monad, + val runF: StateTFunKind +) : StateTKind { + class F private constructor() + + companion object { + inline operator fun invoke(run: StateTFunKind, MF: Monad = monad()): StateT = + StateT(MF, run) + } + + fun map(f: (A) -> B): StateT = + transform { (s, a) -> Tuple2(s, f(a)) } + + fun flatMap(fas: (A) -> StateTKind): StateT = + applyF( + MF.map(runF) { sfsa -> + sfsa.andThen { fsa -> + MF.flatMap(fsa) { + fas(it.b).ev().run(it.a) + } + } + } + , MF) + + fun flatMapF(faf: (A) -> HK): StateT = + applyF( + MF.map(runF) { sfsa -> + sfsa.andThen { fsa -> + MF.flatMap(fsa) { (s, a) -> + MF.map(faf(a)) { b -> Tuple2(s, b) } + } + } + } + , MF) + + fun transform(f: (Tuple2) -> Tuple2): StateT = + applyF( + MF.map(runF) { sfsa -> + sfsa.andThen { fsa -> + MF.map(fsa, f) + } + }, MF) + + fun applyF(runF: StateTFunKind, MF: Monad): StateT = + StateT(MF, runF) + + fun run(initial: S): HK> = + MF.flatMap(runF) { f -> f(initial) } + + fun runA(s: S): HK = + MF.map(run(s)) { it.b } + + fun runS(s: S): HK = + MF.map(run(s)) { it.a } +} + diff --git a/katz/src/test/kotlin/katz/data/StateTests.kt b/katz/src/test/kotlin/katz/data/StateTests.kt index 1ced04f2196..2317f3be90b 100644 --- a/katz/src/test/kotlin/katz/data/StateTests.kt +++ b/katz/src/test/kotlin/katz/data/StateTests.kt @@ -23,27 +23,27 @@ import org.junit.runner.RunWith @RunWith(KTestJUnitRunner::class) class StateTests : UnitSpec() { - private val addOne = State { n -> Tuple2(n + 1, n) } + private val addOne = State({ n -> Tuple2(n * 2, n) }) init { "addOne.run(1) should return Pair(2, 1)" { - addOne.run(1) shouldBe Tuple2(2, 1) + addOne.run(1).ev().value shouldBe Tuple2(2, 1) } "addOne.map(n -> n).run(1) should return same Pair(2, 1)" { - addOne.map { n -> n }.run(1) shouldBe Tuple2(2, 1) + addOne.map { n -> n }.run(1).ev().value shouldBe Tuple2(2, 1) } "addOne.map(n -> n.toString).run(1) should return same Pair(2, \"1\")" { - addOne.map(Int::toString).run(1) shouldBe Tuple2(2, "1") + addOne.map(Int::toString).run(1).ev().value shouldBe Tuple2(2, "1") } "addOne.runS(1) should return 2" { - addOne.runS(1) shouldBe 2 + addOne.runS(1).ev().value shouldBe 2 } "addOne.runA(1) should return 1" { - addOne.runA(1) shouldBe 1 + addOne.runA(1).ev().value shouldBe 1 } } } From 71e013ec5e3753ab6dc7874bfa965b89bfa08925 Mon Sep 17 00:00:00 2001 From: Fernando Franco Giraldez Date: Mon, 12 Jun 2017 08:58:07 +0200 Subject: [PATCH 8/8] Added StateTMonad --- katz/src/main/kotlin/katz/data/StateT.kt | 22 ++++++++++++ .../main/kotlin/katz/instances/StateTMonad.kt | 35 +++++++++++++++++++ 2 files changed, 57 insertions(+) create mode 100644 katz/src/main/kotlin/katz/instances/StateTMonad.kt diff --git a/katz/src/main/kotlin/katz/data/StateT.kt b/katz/src/main/kotlin/katz/data/StateT.kt index d11e0475422..75954d9a9e6 100644 --- a/katz/src/main/kotlin/katz/data/StateT.kt +++ b/katz/src/main/kotlin/katz/data/StateT.kt @@ -1,6 +1,7 @@ package katz typealias StateTKind = HK3 +typealias StateTF = HK2 typealias StateTFun = (S) -> HK> typealias StateTFunKind = HK> @@ -22,6 +23,27 @@ class StateT( fun map(f: (A) -> B): StateT = transform { (s, a) -> Tuple2(s, f(a)) } + fun map2(sb: StateT, fn: (A, B) -> Z): StateT = + applyF(MF.map2(runF, sb.runF) { (ssa, ssb) -> + ssa.andThen { fsa -> + MF.flatMap(fsa) { (s, a) -> + MF.map(ssb(s)) { (s, b) -> Tuple2(s, fn(a, b)) } + } + } + }, MF) + + fun map2Eval(sb: Eval>, fn: (A, B) -> Z): Eval> = + MF.map2Eval(runF, sb.map { it.runF }) { (ssa, ssb) -> + ssa.andThen { fsa -> + MF.flatMap(fsa) { (s, a) -> + MF.map(ssb((s))) { (s, b) -> Tuple2(s, fn(a, b)) } + } + } + }.map { applyF(it, MF) } + + fun product(sb: StateT): StateT> = + map2(sb) { a, b -> Tuple2(a, b) } + fun flatMap(fas: (A) -> StateTKind): StateT = applyF( MF.map(runF) { sfsa -> diff --git a/katz/src/main/kotlin/katz/instances/StateTMonad.kt b/katz/src/main/kotlin/katz/instances/StateTMonad.kt new file mode 100644 index 00000000000..b3318cf00c1 --- /dev/null +++ b/katz/src/main/kotlin/katz/instances/StateTMonad.kt @@ -0,0 +1,35 @@ +package katz + +data class StateTMonad(val MF: Monad) : Monad>, Typeclass { + + override fun flatMap(fa: HK, A>, f: (A) -> HK, B>): StateT = + fa.ev().flatMap(f) + + override fun map(fa: HK, A>, f: (A) -> B): StateT = + fa.ev().map(f) + + override fun pure(a: A): StateT = + StateT(MF, MF.pure({ s: S -> MF.pure(Tuple2(s, a)) })) + + override fun ap(fa: HK, A>, ff: HK, (A) -> B>): StateT = + ff.ev().map2(fa.ev()) { f, a -> f(a) } + + override fun map2(fa: HK, A>, fb: HK, B>, f: (Tuple2) -> Z): StateT = + fa.ev().map2(fb.ev(), { a, b -> f(Tuple2(a, b)) }) + + @Suppress("UNCHECKED_CAST") + override fun map2Eval(fa: HK, A>, fb: Eval, B>>, f: (Tuple2) -> Z): Eval> = + fa.ev().map2Eval(fb as Eval>) { a, b -> f(Tuple2(a, b)) } + + override fun product(fa: HK, A>, fb: HK, B>): HK, Tuple2> = + fa.ev().product(fb.ev()) + + override fun tailRecM(a: A, f: (A) -> HK, Either>): StateT = + StateT(MF, MF.pure({ s: S -> + MF.tailRecM, Tuple2>(Tuple2(s, a), { (s, a) -> + MF.map(f(a).ev().run(s)) { (s, ab) -> + ab.bimap({ a -> Tuple2(s, a) }, { b -> Tuple2(s, b) }) + } + }) + })) +} \ No newline at end of file