Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Instantiating Class which requires a TVar #363

Open
karlmvwaugh opened this issue Sep 27, 2021 · 1 comment
Open

Instantiating Class which requires a TVar #363

karlmvwaugh opened this issue Sep 27, 2021 · 1 comment

Comments

@karlmvwaugh
Copy link

Hello

Having upgraded from 0.8.0 to 0.11.0 I have what is either a question or an issue (I am unsure but raising it as an issue to hopefully at least get an answer).

I have a class that requires a TVar to be passed in at instantiation which is posing an import based issue.

class MyClass(val stuff: TVar[String]) { ... }

Previously (0.8.0) I could have an "import io.github.timwspence.cats.stm.TVar" at the top of my class, this no longer works and from the examples the approach is something akin to

val stm = STM.runtime[IO].unsafeRunSync()
import stm._

but this does not work outside of a class or object?
Is there a way to reference the TVar class without having to instatiate the Stm Runtime?

Please advise if this is a legitimate issue, a misuse issue or just a misunderstanding?
Karl

@TimWSpence
Copy link
Owner

TimWSpence commented Sep 28, 2021

Hi Karl, this is a great question!

Firstly thanks for using cats-stm! :) And for trying to upgrade so quickly to 0.11.0 - that was impressively fast!

The answer to this is somewhat long but I'll do my best!

A bit of history

So in 0.8.0 and earlier I took some shortcuts and implemented STM using non-functional primitives and synchronized. This is actually pretty bad for a cats-effect based library.
Since then, I've re-implemented the entire library based on CE's non-blocking Ref. So we effectively have

class TVar[F[_], A] {
  val state: Ref[F, A]
}

additionally we have some global state

trait STM[F[_]] {
  val globalState: Ref[F, STMState]
}

This kind of sucks. So instead we made TVar a dependent type:

trait STM[F[_]] {
  val globalState: Ref[F, STMState]

  //Note that TVar no longer has to be parameterized on F[_]
  class TVar[A] {
    val state: TVar[F, A]
  }
}

Structuring applications with 0.11.0

As TVar is now an inner class, you need to thread the STM runtime from the point where it is instantiated to where it is used to create TVars or commit Txns.
For methods, this generally means currying:

def foo[F[_], A](stm: STM[F])(tvar: stm.TVar[A] = ???

Class parameters are actually a quite tricky case as you can't have multiple parameter blocks. If TVar were a type member then you could use the Aux pattern but as it is an inner class, I'm actually not sure what you can do. I think the solution might just be for me to make TVar a type member so that the Aux pattern applies. I'll try to play around with it and let you know how I get on.

TLDR

There is not a way to reference TVar without its enclosing STM[F] instance. I believe that this is essential complexity rather than bad design.
In general the solution to this is multiple parameter blocks but in your use-case (class parameters) this is not possible. It definitely should be possible so I'll see what I can do to fix it.

And please do upgrade once I've fixed the issues! I know that 0.8.0 has an easier API to use but its internals really are quite unperformant and will steal threads from your compute pool to do blocking operations.

I hope that answers some of your questions at least. Do let me know if you have more questions!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants