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

Convert StateT to IndexedStateT #1775

Merged
merged 16 commits into from
Sep 11, 2017

Conversation

iravid
Copy link
Contributor

@iravid iravid commented Jul 30, 2017

Resolves #1773.

I converted this rather mechanically, so please point out any flaws if you notice. The files will be renamed once discussion is done; it's hard to see the diffs once they're renamed.

Points for discussion:

  • what additional coverage is needed? I added a basic usage test and the contravariant/profunctor laws.
  • I duplicated the methods in IndexedStateT's companion object to StateTFunctions to keep versions with less type parameters. This helps because you usually need to write them out due to bad inference in for comprehensions. Does this make sense?
  • I kept transformS around to minimize breakage, but it seems a bit out of place.
  • The Contravariant instance is on the SA parameter, while the Functor instance is on the A parameter. Compare this to RWST where the Contravariant instance is for the Reader part.
  • The Profunctor instance is on the SA, SB parameters while on RWST it is on the Reader and result parameters. This seems inconsistent.

Copy link
Contributor

@kailuowang kailuowang left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking providing a StateT implemented in IndexedStateT to minimize API breakage, I.e people currently using StateT would need little to none code change in migration.

@andyscott
Copy link
Contributor

👍 for keeping a StateT around, even if it's just backed by or aliased to IndexedStateT.

@iravid
Copy link
Contributor Author

iravid commented Jul 31, 2017 via email

@andyscott
Copy link
Contributor

Aah yes. That seems like it'll do the trick! Thanks.

@codecov-io
Copy link

codecov-io commented Jul 31, 2017

Codecov Report

Merging #1775 into master will increase coverage by 0.15%.
The diff coverage is 100%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #1775      +/-   ##
==========================================
+ Coverage    95.2%   95.35%   +0.15%     
==========================================
  Files         248      248              
  Lines        4379     4396      +17     
  Branches      118      116       -2     
==========================================
+ Hits         4169     4192      +23     
+ Misses        210      204       -6
Impacted Files Coverage Δ
core/src/main/scala/cats/data/package.scala 85.71% <ø> (ø) ⬆️
core/src/main/scala/cats/data/IndexedStateT.scala 100% <100%> (ø)
...rc/main/scala/cats/laws/discipline/Arbitrary.scala 92.06% <100%> (ø) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 75bc24a...995d451. Read the comment docs.

@iravid
Copy link
Contributor Author

iravid commented Jul 31, 2017

Coverage should hopefully improve after the last push. Any thoughts about the discussion points above?

def transformF[G[_], B](f: F[(S, A)] => G[(S, B)])(implicit F: FlatMap[F], G: Applicative[G]): StateT[G, S, B] =
StateT(s => f(run(s)))
def transformF[G[_], B, SC](f: F[(SB, A)] => G[(SC, B)])(implicit F: FlatMap[F], G: Applicative[G]): IndexedStateT[G, SA, SC, B] =
IndexedStateT(s => f(run(s)))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not covered, maybe a doctest?

override def ap[A, B](ff: StateT[F, S, A => B])(fa: StateT[F, S, A]): StateT[F, S, B] =
StateT[F, S, B]((s: S) =>
override def ap[A, B](ff: IndexedStateT[F, S, S, A => B])(fa: IndexedStateT[F, S, S, A]): IndexedStateT[F, S, S, B] =
IndexedStateT[F, S, S, B]((s: S) =>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not covered, which is a bit surprising. Haven't go the time to investigate further.

@kailuowang
Copy link
Contributor

My thoughts on your points for discussion.

  1. coverage - added some comment on code.
  2. can we still separate them into two different traits? StateTFunctions and IndexStateTFunctions? IndexStateT companion can inherit both, while you can have an object StateT inherit StateTFunctions, what do you think?
  3. Leave transformS in the StateTFunctions mentioned above?
  4. I'm not sure about the last two either.

@iravid
Copy link
Contributor Author

iravid commented Aug 1, 2017

Thanks for your comments @kailuowang. I'll address the feedback regarding coverage.

Regarding the functions in the companion object, I'll move the common ones into a StateConstructors trait and keep the distinct ones where they are. This should solve most of the duplication.

Regarding transformS - it's currently on the StateT instances themselves, so I don't see how we can work out a solution where it lives on the companion objects.

@iravid
Copy link
Contributor Author

iravid commented Aug 4, 2017

@kailuowang addressed your comments

checkAll("IndexedStateT[ListWrapper, Int, Int, Int]", AlternativeTests[IndexedStateT[ListWrapper, Int, Int, ?]](SA).monoidK[Int])
checkAll("IndexedStateT[ListWrapper, Int, Int, Int]", ApplicativeTests[IndexedStateT[ListWrapper, Int, Int, ?]](SA).applicative[Int, Int, Int])
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AlternativeTests already runs the tests from ApplicativeTests, is there a reason to run them explicitly?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm thanks. I wasn't aware of that. Was trying to solve the missing coverage for the ap override on the Alternative instance, but I now realize that this is pointless as there shouldn't be explicit overrides for Applicative methods for StateT. @djspiewak removed them in #1735.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So I'm going to just get rid of this override.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think you can remove ap here as it is needed to implement the Alternative instance?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, to solve it I added with IndexedStateTMonad to the instantiation in the instances trait

* `StateT[F, S, A]` is similar to `Kleisli[F, S, A]` in that it takes an `S`
* argument and produces an `A` value wrapped in `F`. However, it also produces
* an `S` value representing the updated state (which is wrapped in the `F`
* context along with the `A` value.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we move the old StateT documentation to the StateT type alias in package.scala?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure

@iravid
Copy link
Contributor Author

iravid commented Aug 4, 2017

So how does this look to you @kailuowang @peterneyens? I'll start working on IRWST soon in hopes we could get this merged in time for RC1.

def empty[A]: StateT[F, S, A] =
StateT.lift[F, S, A](G.empty[A])(G)

override def ap[A, B](ff: StateT[F, S, A => B])(fa: StateT[F, S, A]): StateT[F, S, B] =
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am curious why remove this override?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The specific overrides for Applicative were removed in #1735, so my reasoning was that this override wasn't necessary either, and it suffices to use the implementation provided by the Monad instance

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, you made IndexedStateTAlternative require mixing with a Monad instance trait. Left some comment below.

implicit def catsDataAlternativeForStateT[F[_], S](implicit FM: Monad[F], FA: Alternative[F]): Alternative[StateT[F, S, ?]] =
new StateTAlternative[F, S] { implicit def F = FM; implicit def G = FA }
private[data] sealed trait IndexedStateTInstances extends IndexedStateTInstances1 {
implicit def catsDataAlternativeForIndexedStateT[F[_], S](implicit FM: Monad[F], FA: Alternative[F]): Alternative[StateT[F, S, ?]] =
Copy link
Contributor

@kailuowang kailuowang Aug 4, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder what if you make this instance an Alternative[StateT[F, S, ?]] with Monad[StateT[F, S, ?]] and let IndexedStateTAlternative inherit IndexedStateTMonad. Since without the ap, IndexedStateTAlternative can't be used without mixin with IndexedStateTMonad
Do you still need to override pure?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will try :-) would that approach look cleaner? Personally I have no preference.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Definitely cleaner

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make the return type Alternative[StateT[F, S, ?]] with Monad[StateT[F, S, ?]] here as well?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @kailuowang - I actually changed Alternative to extend Monad, so there's no need for the intersection type now.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's what I meant. Since you are in effect returning an Alternative with Monad, why hide the fact that it's a Monad? It has a lot more power than the return type suggests. I just felt a bit odd that your return type isn't the actual public type of the instance. Odd code take a bit more time for code reader to grasp, that's all.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My reasoning was that this was about implicit search priorities: let the catsDataAlternativeForIndexedStateT return only an Alternative instance, and let the catsDataMonadForIndexedStateT handle the Monad instance, while hiding the fact that the Alternative instance in fact uses the Monad instance.

If you find that returning the intersection type makes more sense, I'll fix that right away :-) Just haven't seen this done anywhere else in the codebase.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have examples of returning intersection type.
https://github.com/typelevel/cats/blob/master/core/src/main/scala/cats/instances/list.scala#L11
IIRC, at this position instance use to be a MonadCombine which is MonadFilter with Alternative, so I don't have issues with this instance being a Monad with Alternative

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool! Thanks. Changed.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for putting up with my nitpicking! I will find time to go through the whole PR tomorrow or the day after.

@kailuowang
Copy link
Contributor

@iravid , I left a comment, will continue on it later (probably sometime next week though)

@kailuowang kailuowang added this to the 1.0.0-RC1 milestone Aug 4, 2017
transform { case (s, a) => (s, f(a)) }

def contramap[S0](f: S0 => SA)(implicit F: Monad[F]): IndexedStateT[F, S0, SB, A] =
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

doesn't this only need Applicative[F]?

(pure(f), runF).map2(_.andThen(_)) I think does it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes! That's awesome, thank you :-)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Downgraded dimap too

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for the noise. Turns out it's just Functor that's needed for both.

@iravid
Copy link
Contributor Author

iravid commented Aug 8, 2017

Any other comments anyone?

@iravid
Copy link
Contributor Author

iravid commented Aug 16, 2017

Any additional feedback would be appreciated, folks

Copy link
Contributor

@kailuowang kailuowang left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, sorry it took so long for me to finish my review.

@iravid
Copy link
Contributor Author

iravid commented Aug 31, 2017

No worries. As @non mentioned in the RWST PR, I'd like to rename the files once reviews are done. So please hold off on merging after another reviewer has finished reviewing :-)

private[data] sealed trait StateTInstances extends StateTInstances1 {
implicit def catsDataAlternativeForStateT[F[_], S](implicit FM: Monad[F], FA: Alternative[F]): Alternative[StateT[F, S, ?]] =
new StateTAlternative[F, S] { implicit def F = FM; implicit def G = FA }
private[data] sealed trait IndexedStateTInstances extends IndexedStateTInstances1 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

per #1879 do you mind change all the instance traits here into abstract class? I am creating a PR to change other classes, but don't want to touch this one to avoid conflict.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@iravid
Copy link
Contributor Author

iravid commented Sep 8, 2017

Ping. This has been sitting here for a while. Any chance we could move this forward?

@kailuowang
Copy link
Contributor

@iravid github notification may not reach the people you want to reach ( they got so many of them). maybe you could ping the two other reviewers in gitter cats-dev?

@iravid
Copy link
Contributor Author

iravid commented Sep 8, 2017

Good point @kailuowang :-)

Copy link
Contributor

@johnynek johnynek left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍
This looks good to me. Thanks for pushing this through and sorry I hadn't looked sooner.

* an `S` value representing the updated state (which is wrapped in the `F`
* context along with the `A` value.
*
* `IndexedStateT[F, SA, SB, A]` is a stateful computation in a context `F` yielding
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you comment about the Indexed name here? I don't know the context, though I can understand the rest of the comments.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @johnynek. I refined the scaladoc here. See if that makes it more descriptive.

@iravid
Copy link
Contributor Author

iravid commented Sep 10, 2017

@kailuowang Let me know if this is sufficient review-wise and I will rename the files before merge :-)

@johnynek
Copy link
Contributor

Okay. Good point about Arrow.

I'd now probably say we shouldn't add it since just using Monoid feels rather lawless to me. Not clear the monoid empty is what you want since we don't combine on these values.

What do you think?

We could keep as you have it, drop Arrow, or make the Arrow def accept the values to use within lift, which means it can't be implicit but at least saves someone the work of implementing it if they want it (although implementing it isn't that much work).

@iravid
Copy link
Contributor Author

iravid commented Sep 11, 2017

@johnynek I would lean towards removing it entirely in that case, but I had never used Arrow and friends before, so I don't have clear intuition here.

We could keep Strong as it does not involve monoids on the value type.

@johnynek
Copy link
Contributor

johnynek commented Sep 11, 2017 via email

@iravid
Copy link
Contributor Author

iravid commented Sep 11, 2017 via email

@johnynek
Copy link
Contributor

👍

@kailuowang kailuowang merged commit 5bb0252 into typelevel:master Sep 11, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants