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

Consider facility to view State as Reader #179

Open
benhutchison opened this issue Jan 11, 2020 · 7 comments
Open

Consider facility to view State as Reader #179

benhutchison opened this issue Jan 11, 2020 · 7 comments

Comments

@benhutchison
Copy link
Member

benhutchison commented Jan 11, 2020

It can be convenient to allow a MonadState[F, S] to serve as an ApplicativeAsk[F, S]. We can think of the former constraint as the ability to read and update system state, and the latter as simply the ability to read it. If all we need is to read the state, the least privilege principle suggests that a ApplicativeAsk[F, S] constraint is preferable.

Traditionally, Reader monad is associated with reading outside configuration. However, IME it also works well to read the domain model of a stateful application, or some part thereof eg by the lensing techniques available in meow-mtl.

An example of value can be found in the meow-mtl readme use case

This is a proposal to consider an opt-in extra derivation that provides an ApplicativeAsk[F, S] should a MonadState[F, S] instance be available. "Opt-in" rather than automatic because the Ask- and State- may be unrelated, if S-type is something common like Int.

@benhutchison
Copy link
Member Author

benhutchison commented Jan 16, 2020

So it turns out this was removed by @LukaJCB , see #31

What about a happy medium to allow opt-in through an optional extra import, rather than the always-on that was there previously? perhaps something likeimport cats-mtl.ApplicativeAsk.deriveFromState[S] enables the conversion for a type S ?

Also discovered that way back @edmundnoble wrote a law that would preclude it, but this law was later removed and now weakened to something that does permit State as Ask.

For myself, I find it useful to view State as Ask, to signal in the type-signature that some code cannot update the current state. I'm not sure what the arguments against are based on, beyond the tradition that "readers read from an immutable environment". State is immutable in that sense as well, I'll remind viewers.

@SystemFw
Copy link
Contributor

, beyond the tradition that "readers read from an immutable environment". State is immutable in that sense as well, I'll remind viewers.

I don't think I agree with this sentence (although I haven't made up my mind with respect to the entire argument).

Readers reading from an immutable environment boils down to expecting:

(ask, ask).tupled <-> ask.map(x => (x,x))

i.e. asking twice gives you the same result. MonadState is consistent with this traditionally, because traditionally MonadState has been exclusively used with sequential state a la StateT, so unless you are explicitly putting a state operation in between asks, you have no problems.

Increasingly often though, MonadState is used to represent concurrent state (e.g. by backing it with a Ref), and that would break the assumption

So I feel like this issue is related to #120
Where basically we need to decide what we want MonadState to be.

Also, I think things get more interesting once you consider local as well, which is literally about having local only mutation, which can also implemented with StateT but definitely breaks with concurrent state

@SystemFw
Copy link
Contributor

SystemFw commented Jan 16, 2020

Sorry, I probably haven't been clear in what I think of the overall change: I don't mind, but we need
to get a consistent picture of how all the classes work together.

An alternative would be to expose weaker classes as an addition to what we have: e.g. a ConcurrentState and MutableAsk

@benhutchison
Copy link
Member Author

benhutchison commented Jan 16, 2020

I admit my previous experiences have used sequential, deterministic State and I hadn't considered the concurrent case.

Seems like there are two separable dimensions here:

  1. Whether a piece of code needs ReadWrite or merely ReadOnly access to some state. That distinction seems to me important to encode into the type signature, and is the goal Im trying to hit by raising this issue. I'm not sure if/how I can achieve that with current Cats MTL typeclasses.

  2. Whether the system state is sequential&deterministic or concurrent&non-deterministic.

A question: from library user's POV, does it help or hinder them by requiring different type-classes (TCs) to read/write from concurrent vs sequential state? Different TCs imply that the choice of state representations (ie concurrent vs sequential) is baked into the code when it's written, whereas it seems (superficially) appealing to have that as a policy decision the integrator can vary later.

@SystemFw
Copy link
Contributor

whereas it seems (superficially) appealing to have that as a policy decision the integrator can vary later.

Yeah, but the sort of code you can write sanely hugely depends on that. For example, having modify be a non-primitive get andThen set is only valid in the non-concurrent scenario.

Whether a piece of code needs ReadWrite or merely ReadOnly access to some state.

Yeah, fwiw I don't think this request is unreasonable, it's just not what ApplicativeAsk has been intended for traditionally

@benhutchison
Copy link
Member Author

benhutchison commented Jan 16, 2020

(at the risk of restating a pre-existing discussion) So are there four concepts here that could potentially each be a typeclass?

AtomicAsk: ReadOnly access to non-deterministic state (borrowing @kubukoz's naming here ). Weaker laws

AtomicState: ReadWrite access to non-deterministic state. Weaker laws

ApplicativeAsk: ReadOnly access to deterministic/immutable state/environment.

MonadState: ReadWrite access to deterministic sequential state

If the latter two are "sub-types" of, or imply, the former two, then writing a program in terms of the looser constraints (ie AtomicAsk, AtomicState) might retain the ability to vary the state representation at "integration-time" ie after code has been written, which seems nice-to-have .

@BenHutchisonSeek
Copy link

This kind of stalled with concerns around concurrent state #120, but I dont think this issue need be entangled with that.

Having considered both threads, Im convinced is reasonable and safe to opt-in to viewing State/MonadState as Reader.

The thing that's not reasonable is to use State/MonadState with Ref because it breaks the state laws around getting back what you set due to potential concurrent modification. A weaker concurrent state abstraction is needed.

Specifically, I propose import cats-mtl.ApplicativeAsk.deriveFromState to opt-in to the conversion.

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

3 participants