7

I have a class that has a one parameter method that produces a result and returns an object like itself but with updated state for subsequent use.

For example, below contains a simple example of such a class and how I might use it:

case class Foo(x: Double) {
  def bar(y: Double): (Foo, Double) = (Foo(x + y), x / (x + y))
}

val res = Vector(1.0,2.0,3.0,4.0).foldLeft((Foo(0), 0.0))((foo, x) => foo._1.bar(x))

res._1.bar(3.0)

I have looked at the Cats State monad and was hopeful that I could use it to avoid threading the state (the "x" member) around. The examples here are close to what I want but the function that returns the new state does not have any parameters and state is not passed around in a loop-like operation (instead it's passed between expressions). I am a complete novice when it comes to Cats but am I barking up the wrong tree?

Richard Redding
  • 327
  • 2
  • 11
  • 2
    "avoid threading the state (the `x` member) around" is not a well-defined goal. If you leave out `x` in the definition of `Foo`, then what is left? And if nothing's left, then why do you need `Foo` in the first place? Saying what you *don't want* is not enough, you should also explain what you *do want*. Do you want to replace `Foo` by `State`? – Andrey Tyukin Dec 19 '18 at 13:25
  • 2
    The code is equivalent to the trivial `val s = Vector(1.0,2.0,3.0,4.0).sum; println(s / (s + 3.0))`, nothing of interest is accumulated during the computation (second component is dropped in every step). Unclear what you're asking. – Andrey Tyukin Dec 19 '18 at 14:15
  • Hi Andrey, I'll have a think and try to articulate what I want more precisely. However, the example is just an example, obviously I could come up with a more complicated object but the above is just something to point at. – Richard Redding Dec 19 '18 at 14:25

1 Answers1

4

Below you can find the way how cats state monad can be adapated to your case. However I had some problems with sequencing List[State[S, A]] to have State[S, List[A]] in cats, so I have written a function sequence for that. If someone knew how to do that I would be interested :)

import cats.data._

case class Foo(x: Double)

def bar(y: Double): State[Foo, Double] = for {
  foo <- State.get[Foo]
  _ <- State.set(Foo(foo.x + y))

} yield foo.x / (foo.x + y)

val xs: List[State[Foo, Double]] = List(1.0, 2.0, 3.0, 4.0).map(bar)

def sequence(xs: List[State[Foo, Double]]): State[Foo, List[Double]] =
  xs.foldLeft(State.pure[Foo, List[Double]](List.empty[Double])) { (acc, x) =>
    for {
      xs <- acc
      xx <- x
    } yield xx :: xs
  }

val s = sequence(xs)
val ss = s.map(_.head)

s.run(Foo(0)).value
ss.run(Foo(0)).value

ss.flatMap(_ => bar(3)).run(Foo(0)).value

In result you get

res0: (Foo, List[Double]) = (Foo(10.0),List(0.6, 0.5, 0.3333333333333333, 0.0))
res1: (Foo, Double) = (Foo(10.0),0.6)

res2: (Foo, Double) = (Foo(13.0),0.7692307692307693)
slavik
  • 1,223
  • 15
  • 17
  • 4
    Here's one way to get sequence to work via cats: [gist](https://gist.github.com/nathaniel-may/8876978dc9275c1ee01d45b956efe7e6) – codenoodle Dec 21 '18 at 00:32
  • 4
    The recommended way is to add `scalacOptions in Compile += "-Ypartial-unification"` to your build. – Oleg Pyzhcov Dec 21 '18 at 22:30
  • 1
    Hi codenoodle, I have reached almost the same before but I had compilation errors. I don't know what I have missed. Thanks by the way. – slavik Dec 22 '18 at 17:57