diff --git a/core/src/main/scala/cats/data/StateT.scala b/core/src/main/scala/cats/data/StateT.scala index 864c869905..13799da11f 100644 --- a/core/src/main/scala/cats/data/StateT.scala +++ b/core/src/main/scala/cats/data/StateT.scala @@ -133,6 +133,21 @@ object StateT extends StateTInstances { def pure[F[_], S, A](a: A)(implicit F: Applicative[F]): StateT[F, S, A] = StateT(s => F.pure((s, a))) + + def lift[F[_], S, A](fa: F[A])(implicit F: Applicative[F]): StateT[F, S, A] = + StateT(s => F.map(fa)(a => (s, a))) + + def inspect[F[_], S, A](f: S => A)(implicit F: Applicative[F]): StateT[F, S, A] = + StateT(s => F.pure((s, f(s)))) + + def inspectF[F[_], S, A](f: S => F[A])(implicit F: Applicative[F]): StateT[F, S, A] = + StateT(s => F.map(f(s))(a => (s, a))) + + def modify[F[_], S](f: S => S)(implicit F: Applicative[F]): StateT[F, S, Unit] = + StateT(s => F.pure((f(s), ()))) + + def modifyF[F[_], S](f: S => F[S])(implicit F: Applicative[F]): StateT[F, S, Unit] = + StateT(s => F.map(f(s))(s => (s, ()))) } private[data] sealed trait StateTInstances extends StateTInstances1 { diff --git a/tests/src/test/scala/cats/tests/StateTTests.scala b/tests/src/test/scala/cats/tests/StateTTests.scala index e7ebd9e290..43e4c77a36 100644 --- a/tests/src/test/scala/cats/tests/StateTTests.scala +++ b/tests/src/test/scala/cats/tests/StateTTests.scala @@ -29,6 +29,46 @@ class StateTTests extends CatsSuite { } } + test("State.inspect and StateT.inspect are consistent") { + forAll { (s: String, f: String => Int) => + val state: State[String, Int] = State.inspect(f) + val stateT: State[String, Int] = StateT.inspect(f) + state.run(s) should === (stateT.run(s)) + } + } + + test("State.inspect and StateT.inspectF are consistent") { + forAll { (s: String, f: String => Int) => + val state: State[String, Int] = State.inspect(f) + val stateT: State[String, Int] = StateT.inspectF(f.andThen(Eval.now)) + state.run(s) should === (stateT.run(s)) + } + } + + test("State.modify and StateT.modify are consistent") { + forAll { (s: String, f: String => String) => + val state: State[String, Unit] = State.modify(f) + val stateT: State[String, Unit] = StateT.modify(f) + state.run(s) should === (stateT.run(s)) + } + } + + test("State.modify and StateT.modifyF are consistent") { + forAll { (s: String, f: String => String) => + val state: State[String, Unit] = State.modify(f) + val stateT: State[String, Unit] = StateT.modifyF(f.andThen(Eval.now)) + state.run(s) should === (stateT.run(s)) + } + } + + test("State.pure and StateT.lift are consistent") { + forAll { (s: String, i: Int) => + val state: State[String, Int] = State.pure(i) + val stateT: State[String, Int] = StateT.lift(Eval.now(i)) + state.run(s) should === (stateT.run(s)) + } + } + test("Cartesian syntax is usable on State") { val x = add1 *> add1 x.runS(0).value should === (2)