Skip to content

Commit

Permalink
Merge pull request #782 from ceedubs/state-eval
Browse files Browse the repository at this point in the history
Use Eval instead of Trampoline for State
  • Loading branch information
adelbertc committed Jan 8, 2016
2 parents a13f4f7 + d338150 commit 0529841
Show file tree
Hide file tree
Showing 5 changed files with 18 additions and 28 deletions.
11 changes: 2 additions & 9 deletions core/src/main/scala/cats/state/StateT.scala
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
package cats
package state

import cats.free.Trampoline
import cats.data.Kleisli
import cats.std.function.function0Instance

/**
* `StateT[F, S, A]` is similar to `Kleisli[F, S, A]` in that it takes an `S`
Expand Down Expand Up @@ -125,7 +123,7 @@ object StateT extends StateTInstances {
StateT(s => F.pure((s, a)))
}

private[state] sealed abstract class StateTInstances extends StateTInstances0 {
private[state] sealed abstract class StateTInstances {
implicit def stateTMonadState[F[_], S](implicit F: Monad[F]): MonadState[StateT[F, S, ?], S] =
new MonadState[StateT[F, S, ?], S] {
def pure[A](a: A): StateT[F, S, A] =
Expand All @@ -143,17 +141,12 @@ private[state] sealed abstract class StateTInstances extends StateTInstances0 {
}
}

private[state] sealed abstract class StateTInstances0 {
implicit def stateMonadState[S]: MonadState[State[S, ?], S] =
StateT.stateTMonadState[Trampoline, S]
}

// To workaround SI-7139 `object State` needs to be defined inside the package object
// together with the type alias.
private[state] abstract class StateFunctions {

def apply[S, A](f: S => (S, A)): State[S, A] =
StateT.applyF(Trampoline.done((s: S) => Trampoline.done(f(s))))
StateT.applyF(Now((s: S) => Now(f(s))))

/**
* Return `a` and maintain the input state.
Expand Down
4 changes: 1 addition & 3 deletions core/src/main/scala/cats/state/package.scala
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package cats

import free.Trampoline

package object state {
type State[S, A] = StateT[Trampoline, S, A]
type State[S, A] = StateT[Eval, S, A]
object State extends StateFunctions
}
11 changes: 5 additions & 6 deletions docs/src/main/tut/state.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,6 @@ Let's write a new version of `nextLong` using `State`:

```tut:silent
import cats.state.State
import cats.std.function._
val nextLong: State[Seed, Long] = State(seed =>
(seed.next, seed.long))
Expand All @@ -159,16 +158,16 @@ val createRobot: State[Seed, Robot] =
} yield Robot(id, sentient, name, model)
```

At this point, we have not yet created a robot; we have written instructions for creating a robot. We need to pass in an initial seed value, and then we can call `run` to actually create the robot:
At this point, we have not yet created a robot; we have written instructions for creating a robot. We need to pass in an initial seed value, and then we can call `value` to actually create the robot:

```tut
val (finalState, robot) = createRobot.run(initialSeed).run
val (finalState, robot) = createRobot.run(initialSeed).value
```

If we only care about the robot and not the final state, then we can use `runA`:

```tut
val robot = createRobot.runA(initialSeed).run
val robot = createRobot.runA(initialSeed).value
```

The `createRobot` implementation reads much like the imperative code we initially wrote for the mutable RNG. However, this implementation is free of mutation and side-effects. Since this code is referentially transparent, we can perform the refactoring that we tried earlier without affecting the result:
Expand All @@ -189,11 +188,11 @@ val createRobot: State[Seed, Robot] = {
```

```tut
val robot = createRobot.runA(initialSeed).run
val robot = createRobot.runA(initialSeed).value
```

This may seem surprising, but keep in mind that `b` isn't simply a `Boolean`. It is a function that takes a seed and _returns_ a `Boolean`, threading state along the way. Since the seed that is being passed into `b` changes from line to line, so do the returned `Boolean` values.

## Fine print

TODO explain StateT and the fact that State is an alias for StateT with trampolining.
TODO explain StateT and the fact that State is an alias for StateT with Eval.
18 changes: 9 additions & 9 deletions tests/src/test/scala/cats/tests/StateTTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,40 +3,40 @@ package tests

import cats.laws.discipline.{MonoidalTests, MonadStateTests, MonoidKTests, SerializableTests}
import cats.state.{State, StateT}
import cats.tests.FreeTests._
import cats.laws.discipline.eq._
import cats.laws.discipline.arbitrary._
import org.scalacheck.{Arbitrary, Gen}

class StateTTests extends CatsSuite {
import StateTTests._

test("basic state usage"){
add1.run(1).run should === (2 -> 1)
add1.run(1).value should === (2 -> 1)
}

test("traversing state is stack-safe"){
val ns = (0 to 100000).toList
val x = ns.traverseU(_ => add1)
x.runS(0).run should === (100001)
x.runS(0).value should === (100001)
}

test("State.pure and StateT.pure are consistent"){
forAll { (s: String, i: Int) =>
val state: State[String, Int] = State.pure(i)
val stateT: State[String, Int] = StateT.pure(i)
state.run(s).run should === (stateT.run(s).run)
state.run(s) should === (stateT.run(s))
}
}

test("Monoidal syntax is usable on State") {
val x = add1 *> add1
x.runS(0).run should === (2)
x.runS(0).value should === (2)
}

test("Singleton and instance inspect are consistent"){
forAll { (s: String, i: Int) =>
State.inspect[Int, String](_.toString).run(i).run should === (
State.pure[Int, Unit](()).inspect(_.toString).run(i).run)
State.inspect[Int, String](_.toString).run(i) should === (
State.pure[Int, Unit](()).inspect(_.toString).run(i))
}
}

Expand Down Expand Up @@ -109,10 +109,10 @@ class StateTTests extends CatsSuite {

object StateTTests extends StateTTestsInstances {
implicit def stateEq[S:Eq:Arbitrary, A:Eq]: Eq[State[S, A]] =
stateTEq[free.Trampoline, S, A]
stateTEq[Eval, S, A]

implicit def stateArbitrary[S: Arbitrary, A: Arbitrary]: Arbitrary[State[S, A]] =
stateTArbitrary[free.Trampoline, S, A]
stateTArbitrary[Eval, S, A]

val add1: State[Int, Int] = State(n => (n + 1, n))
}
Expand Down
2 changes: 1 addition & 1 deletion tests/src/test/scala/cats/tests/WordCountTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ class WordCountTest extends CatsSuite {
val wordCountState = allResults.first.first
val lineCount = allResults.first.second
val charCount = allResults.second
val wordCount = wordCountState.runA(false).run
val wordCount = wordCountState.runA(false).value
charCount.getConst should === (96)
lineCount.getConst should === (2)
wordCount.getConst should === (17)
Expand Down

0 comments on commit 0529841

Please sign in to comment.