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

Replace TransLift with MonadTrans #1621

Merged
merged 5 commits into from
May 18, 2017
Merged

Conversation

wedens
Copy link
Contributor

@wedens wedens commented Apr 22, 2017

Resolves #1613.

Pros:

  • MonadTrans is lawful (well... almost). There are identity (liftT . pure = pure) and composition (liftT (m >>= f) = liftT m >>= (liftT . f)) laws. I think for MonadTrans to be really lawful it should extend (in current encoding) Monad. But it will make it unusable for the same reason MonadError et al is (diamonds 💎 )
  • Machinery like TLExtract is not required
  • Can be used as a typeclass without dealing with varying typeclass (Unable to use TransLift as a typeclass #1613 is about it)

Cons:

  • Monad constraint is imposed on for any base functor. For some transformers (e.g. Kleisli) liftT can be implemented with weaker constraint

/cc @djspiewak @ceedubs

@wedens wedens changed the title Replaced TransLift with MonadTrans Replace TransLift with MonadTrans Apr 22, 2017
@codecov-io
Copy link

codecov-io commented Apr 22, 2017

Codecov Report

Merging #1621 into master will decrease coverage by 0.03%.
The diff coverage is 90%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #1621      +/-   ##
==========================================
- Coverage   93.37%   93.33%   -0.04%     
==========================================
  Files         240      241       +1     
  Lines        3937     3948      +11     
  Branches      138      135       -3     
==========================================
+ Hits         3676     3685       +9     
- Misses        261      263       +2
Impacted Files Coverage Δ
core/src/main/scala/cats/data/EitherT.scala 96.42% <ø> (ø) ⬆️
core/src/main/scala/cats/syntax/monadTrans.scala 0% <0%> (ø)
core/src/main/scala/cats/data/OptionT.scala 100% <100%> (ø) ⬆️
free/src/main/scala/cats/free/FreeT.scala 93.58% <100%> (ø) ⬆️
core/src/main/scala/cats/data/WriterT.scala 93.47% <100%> (ø) ⬆️
...n/scala/cats/laws/discipline/MonadTransTests.scala 100% <100%> (ø)
laws/src/main/scala/cats/laws/MonadTransLaws.scala 100% <100%> (ø)
core/src/main/scala/cats/data/StateT.scala 97.43% <100%> (ø) ⬆️
core/src/main/scala/cats/data/Kleisli.scala 92.64% <100%> (ø) ⬆️
... and 4 more

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 b6fc4e8...d39d2f9. Read the comment docs.

@djspiewak
Copy link
Member

This gets a 👍 from me. I think recent efforts really prove that we weren't gaining anything from the flexibility of TransLift, and if anything we were losing a lot due to the insane complexity of getting the types to work out. The laws point is also a kicker. It was a really neat experiment, but I think it's fair to say that it failed. I would rather have MonadTrans.

@wedens
Copy link
Contributor Author

wedens commented Apr 24, 2017

CI on 2.11.8 fails due to "The job exceeded the maximum time limit for jobs, and has been terminated.". Using coursier may chop off some build time.

/**
* A type class which abstracts over the ability to lift an M[A] into a
* MonadTransformer
*/
Copy link
Contributor

Choose a reason for hiding this comment

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

nit here: the scala doc style in cats is

/**
 *
 */

i.e. all stars aligned with the first star.

Copy link
Contributor

Choose a reason for hiding this comment

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

other than that it's a 👍 from me.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fixed

trait MonadTrans[MT[_[_], _]] {
/**
* Lift a value of type M[A] into a monad transformer MT[M, A]
*/
Copy link
Contributor

Choose a reason for hiding this comment

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

And this one. also maybe a blank line above?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

haven't noticed it ;)

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. Thanks very much!

@ceedubs
Copy link
Contributor

ceedubs commented Apr 24, 2017

Does anyone know of any data structures that require Monad to lift as opposed to just Applicative (or weaker)? I don't see any examples currently inside Cats, which makes me wonder if it should be ApplicativeTrans. However, I guess the downside with that is that you lose the composition law?

@wedens
Copy link
Contributor Author

wedens commented Apr 24, 2017

@ceedubs ContT, I think

@djspiewak
Copy link
Member

Apparently Codensity does as well. @sellout and I had this conversation last week.

@sellout
Copy link
Contributor

sellout commented Apr 24, 2017

@wedens I don’t think ContT, but I’m far from certain.

@ceedubs Is there anything other than Codensity that requires more than Functor? I ask because FunctorK[F[_[_], _]] (or HFunctor, or whatever) is very similar to Scalaz’s Hoist and PointedK (or HPointed) is very similar to MonadTrans, except that the K (or H) variants only allow a Functor constraint (I think), so I thought that perhaps we could have

@typeclass trait PointedK[F[_[_], _]] extends MonadTrans[F] {
  def pointk[G[_]: Functor]: G ~> F[G, ?]

  def liftT[M[_]: Monad, A](ma: M[A]) = pointk[M].apply(ma)
}

(ok, maybe we have to deal with the natural transformation somehow, but you get the gist). Right now, Codensity is the only thing that I’m pretty sure can implement MonadTrans but not PointedK.

And the underlying need for FunctorK, etc. is that with mutually-recursive recursion schemes you end up with a lot of G[_]s that aren’t monads.

@djspiewak
Copy link
Member

djspiewak commented Apr 24, 2017

@sellout I'm pretty sure you can't define runCont without a Monad for the constituent effect of ContT (or at the very least, a FlatMap).

@sellout
Copy link
Contributor

sellout commented Apr 24, 2017

@djspiewak Maybe (I think – again, not sure – you only need Applicative), but that doesn’t mean you can’t liftT with a lesser constraint.

@djspiewak
Copy link
Member

djspiewak commented Apr 24, 2017

@sellout Oh, you're right. The implementation of runCont doesn't even need Applicative. ContT could be done without any constraints at all. I guess that makes sense, since it's in many ways a dual of Kleisli

@sellout
Copy link
Contributor

sellout commented Apr 24, 2017

I’m going to frame that comment.

}

final class MonadTransOps[F[_], A](val fa: F[A]) extends AnyVal {
def liftT[MT[_[_], _]](implicit F: Monad[F], MT: MonadTrans[MT]): MT[F, A] =
Copy link
Contributor

@kailuowang kailuowang Apr 27, 2017

Choose a reason for hiding this comment

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

How about a doctest here? It will provide coverage for both here and the syntax line above. Then you should get green light from codecov

Copy link
Contributor Author

Choose a reason for hiding this comment

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

There is a test in SyntaxTests.

Copy link
Contributor

Choose a reason for hiding this comment

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

I pointed out because codecov reported test miss here. Now I see that SyntaxTest is only a compilation check. I think I prefer a doctest in the type class which actually run the code (thus coverage) and provide a concrete example there. But we can certainly address that in a different PR.

*/
sealed trait Trivial

object Trivial {

Choose a reason for hiding this comment

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

I understand that as a result of this PR Trivial is no longer needed in cats, but it is still a very useful type constraint. Is there a good reason to remove Trivial?

Copy link
Member

Choose a reason for hiding this comment

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

I thought about making the same objection, but my conclusion was that "less is more" in terms of API surface area. Shapeless already has a Trivial, and it is very likely to be on the classpath of anyone who needs this sort of thing. I don't think there's a need for cats to keep it.

Copy link
Contributor

Choose a reason for hiding this comment

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

We are also going to remove unapply to a separate module. Maybe we should copy trivial there too?

@@ -39,7 +39,7 @@ trait AllSyntax
with ShowSyntax
with SplitSyntax
with StrongSyntax
with TransLiftSyntax
with MonadTransSyntax
Copy link
Member

Choose a reason for hiding this comment

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

Keep in alphabetical order?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ok

/**
* Lift a value of type M[A] into a monad transformer MT[M, A]
*/
def liftT[M[_]: Monad, A](ma: M[A]): MT[M, A]
Copy link
Contributor

@adelbertc adelbertc Apr 28, 2017

Choose a reason for hiding this comment

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

One thing Edward Kmett noted in a talk he gave on monad homomorphisms is that MonadTrans provides no constraint that the resulting MT[M, A] has a Monad instance. I believe in his talk he fixes this by adding a function taking the Monad[M] constraint and providing a Monad[MT[M, ?]] constraint. In Scala it might look something like:

implicit def mtMonad[M[_]: Monad]: Monad[MT[M, ?]]

Would we want something like that here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

scalaz does it. Not sure if it's useful. But it makes sense in theory.

Copy link
Contributor

Choose a reason for hiding this comment

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

This would communicate the law: For every lawful M[_]: Monad there exists a lawful MT[M, ?]: Monad

Copy link
Member

Choose a reason for hiding this comment

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

Can we check such a law? Would be cool if we could rule out the naïve ListT for instance.

Copy link
Contributor

@adelbertc adelbertc Apr 28, 2017

Choose a reason for hiding this comment

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

A simple way would be to pick a fixed M, e.g. Option, get the Monad[MT[Option, ?]] from this method, and then law-check that.

A fancier way would be to use existentials or something to be able to vary M and do law-checking across all these :-)

EDIT Hmm I actually don't know what that would look like in terms of code though.

@peterneyens
Copy link
Collaborator

Should we merge this as is and add an eventual Monad[MT[M, ?]] law in a follow up PR?

@kailuowang
Copy link
Contributor

Note: https://github.com/milessabin/kittens/pull/37 to move Trivial to kittens.

@wedens
Copy link
Contributor Author

wedens commented May 11, 2017

is there something holding this PR?

@kailuowang
Copy link
Contributor

@wedens I just thumbed up @peterneyens's last comment. @adelbertc what do you think? If you have no issue with that, I think this one is good to merge.

Copy link
Contributor

@edmundnoble edmundnoble left a comment

Choose a reason for hiding this comment

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

LGTM.

@ceedubs
Copy link
Contributor

ceedubs commented May 13, 2017

While it took a while, I'm sold on the fact that TransLift was a failed experiment.

One thing that I didn't realize at first and that I haven't seen come up in comments (other than the original PR description) is that here, MonadError MonadTrans doesn't extend Monad (citing diamonds as the issue). It sounds like there are some plans to provide mechanisms to attach laws to this, but I just wanted to call that out and make sure that people were aware and don't see it being an issue. cc @djspiewak @adelbertc @kailuowang @sellout

@kailuowang
Copy link
Contributor

@ceedubs I just created the issue for adding such a law.
#1674
As for this PR, I think we are good to merge?

@ceedubs
Copy link
Contributor

ceedubs commented May 17, 2017

@tpolecat @edmundnoble @djspiewak are you still happy with this PR in response to #1621 (comment) ? I'm definitely not opposed to this PR being merged, but I respect others' opinion on this much more than my own, because I only rarely use MonadTrans.

@peterneyens
Copy link
Collaborator

I think we may need to change the Translift of ReaderWriterStateT to MonadTrans after #1598.

@djspiewak
Copy link
Member

@ceedubs

One thing that I didn't realize at first and that I haven't seen come up in comments (other than the original PR description) is that here, MonadError doesn't extend Monad (citing diamonds as the issue).

Uh, am I missing something? MonadError isn't touched in this PR.

@ceedubs
Copy link
Contributor

ceedubs commented May 18, 2017

@djspiewak oops I had MonadError on the brain from the Eval discussion. I meant MonadTrans.

@ceedubs
Copy link
Contributor

ceedubs commented May 18, 2017

Wait that makes sense that MonadTrans wouldn't extend Monad. Ignore my previous comments.

@ceedubs
Copy link
Contributor

ceedubs commented May 18, 2017

Okay this has 3 signoffs. I'm going to go ahead and merge it.

@ceedubs ceedubs merged commit b4995b2 into typelevel:master May 18, 2017
@ceedubs
Copy link
Contributor

ceedubs commented May 18, 2017

Oops I had forgotten about #1621 (comment) . I can't take care of it at the moment. I should be able to do it this evening if nobody beats me to it.

@wedens wedens deleted the monad_trans branch May 18, 2017 14:25
@wedens
Copy link
Contributor Author

wedens commented May 18, 2017

@ceedubs I've fixed it #1680

@edmundnoble
Copy link
Contributor

@wedens Just so you know, cats-mtl has its own formulation of MonadTrans which is not compatible with this one. So I will have to remove it when I remove the MTL type classes from cats.

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.