-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
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
Add Representable Functor #2284
Conversation
Codecov Report
@@ Coverage Diff @@
## master #2284 +/- ##
==========================================
+ Coverage 95.03% 95.05% +0.02%
==========================================
Files 338 343 +5
Lines 5878 5931 +53
Branches 210 220 +10
==========================================
+ Hits 5586 5638 +52
- Misses 292 293 +1
Continue to review full report at Codecov.
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this looks interesting to me. I was not aware of it.
I'll try to think about the tailRecM.
@@ -7,7 +7,8 @@ import cats.arrow._ | |||
/** | |||
* Represents a function `A => F[B]`. | |||
*/ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we revert the style changes in this file?
} | ||
} | ||
|
||
def representableIsBimonad[F[_], R](implicit R: Representable.Aux[F, R], M: Monoid[R]): Monad[F] = new Bimonad[F] with StackSafeMonad[F] { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
don't we only get Monad if there is no Monoid[R]
? Should we make that a lower priority implicit?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes thats right. I wasn't sure how to prioritise the implicits when I wrote that instance, but just read up on it. I added RepresentableInstances1
and RepresentableInstances0
R.tabulate(a => R.index(f(R.index(fa)(a)))(a)) | ||
|
||
// TODO: Can we make this monad stack safe? | ||
// override def tailRecM[A, B](a: A)(f: A => F[Either[A, B]]): F[B] = ??? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think something like this works:
R.tabulate { r: R =>
@annotation.tailrec
def loop(a: A): B =
R.index(f(a))(r) match {
case Right(b) => b
case Left(a) => loop(a)
}
loop(a)
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks! I hadn't got around to thinking this through. That solution works.
@@ -23,7 +23,7 @@ class MonadSuite extends CatsSuite { | |||
forAll(smallPosInt) { (max: Int) => | |||
val (result, aggregation) = incrementAndGet.whileM[Vector](StateT.inspect(i => !(i >= max))).run(0) | |||
result should ===(Math.max(0, max)) | |||
aggregation should === ( if(max > 0) (1 to max).toVector else Vector.empty ) | |||
aggregation should === ( if (max > 0) (1 to max).toVector else Vector.empty ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we revert style changes?
@@ -39,13 +39,13 @@ class KleisliSuite extends CatsSuite { | |||
checkAll("Semigroupal[Kleisli[Option, Int, ?]]", SerializableTests.serializable(Semigroupal[Kleisli[Option, Int, ?]])) | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we revert the style changes? I'd prefer a PR with style only changes so we can focus on logic/design.
|
||
type Pair[A] = (A, A) | ||
checkAll("Pair[String, String] <-> Boolean => String", RepresentableTests[Pair, Boolean].representable[String]) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we check the monad/comonad laws on a Representable here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added a check for Bimonad
3b014ea
to
7e54efb
Compare
@johnynek @tpolecat @kailuowang I think this is ready for a final review / approval |
* A generalisation of the Store comonad, for any `Representable` functor. | ||
* `Store` is the dual of `State` | ||
*/ | ||
final case class RepresentableStore[F[_], S, A](fa: F[A])(val index: S)(implicit R: Representable.Aux[F, S]) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would rather not use the second parameter for index. I think scala does not use that for equality (only the first item is checked) and I doing think we want that. Also, I’d rather take R as an implicit parameter at the callsites that use it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All methods in the class depend on the Representable
instance, thats why I shifted it be a constructor parameter. The general reason (that I have seen) to move implicit constraints to methods rather than constructor params, it to avoid over constraining methods that do not require the implicit. I guess the downside is that if additional methods are added, that don't use the Representable
instance, they will be over constrained. Is it worth moving the constraint in this case?
I will remove the second parameter list for index
, it just made the implementation slightly cleaner.
@eli-jordan it looks like there are some duplicated commits created when you merge master to your branch. You might need to hard reset to master and cherry-pick |
} | ||
|
||
private[cats] sealed abstract class RepresentableInstances1 { | ||
def catsRepresentableIsMonad[F[_], R](implicit Rep: Representable.Aux[F, R]): Monad[F] = new RepresentableMonad[F, R] { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This and the one above are not implicit and not much point to be imported and thus no need to follow the implicits naming convention. If we want them to be available for auto derivation of Bimonad
and Monad
we need to put them in a trait extendable by cats.implicits
and better have some tests for potential conflicts. I vote for just rename them to better searchable if there aren't many real world use cases for them.
f9dd8af
to
b99e781
Compare
@kailuowang I have made the suggested change, and cleaned up the commit history. |
|
||
type Pair[A] = (A, A) | ||
|
||
checkAll("Id[Int] <-> Unit => String", RepresentableTests[Id, Unit].representable[String]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
another nit: Id[String]
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice catch! :-)
implicit val bimonadInstance = Representable.bimonad[Pair, Boolean] | ||
checkAll("Pair[Int]", BimonadTests[Pair].bimonad[Int, Int, Int]) | ||
checkAll("Bimonad[Pair]", SerializableTests.serializable(Bimonad[Pair])) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
would you test the monad
as well?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't because BimonadTests
runs the laws for both Monad
and Comonad
plus some others and the implementation is shared. Happy to add one for monad directly though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for adding it. It wasn't a big deal, but just one more red spot on the coverage report that can always cost some attention in the future. Also it would be nice to have it covered in case
someone for whatever reason changes the monad implementation.
* Given a functorial computation on the index `S` peek at the value in that functor. | ||
*/ | ||
def experiment[G[_]](fn: S => G[S])(implicit G: Functor[G]): G[A] = { | ||
G.map(fn(index))(peek) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
mind to add a test for this method? I think a doctest would probably suffice.
@kailuowang Thanks for the review. I have updated based on your comments. |
I'm super late to this PR and I apoligize for that. I've never used representable functors before, but I took a look at the Haskell package you mentioned at the top and I see that it implies a |
@LukaJCB Because you can actually derive distributeRep :: (Representable f, Functor w) => w (f a) -> f (w a)
distributeRep wf = tabulate (\k -> fmap (`index` k) wf) I guess I could have made I added the derived |
Sounds good 👍 We can add it later :) |
Looking good to me. I am not very familiar with the practical use of it but the API looks clean and straightforward. Will be great to have two other review approvals. |
@johnynek any other issues? |
def index(implicit R: Representable.Aux[F, R]): R => A = R.index(fa) | ||
} | ||
|
||
final class TabulateOps[A, R](f: R => A) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not extend AnyVal here and above?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If there is a reason can we comment?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When I first wrote it, I had the ops class taking implicit params and AnyVal doesn't allow multiple param lists. I missed adding it back when I changed that, will fix that up now.
new IndexOps[F, A, R](fa) | ||
} | ||
|
||
final class IndexOps[F[_], A, R](val fa: F[A]) extends AnyVal { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this have R on the class? Maybe it doesn’t matter but it seems like it should be on index.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In my tests it didn't seem to make a difference, but I think you're right, it makes more sense to put it on index. I'll make that change now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
left a comment about possibly adding Representable for Eval.
@@ -77,6 +77,18 @@ package object cats { | |||
override def isEmpty[A](fa: Id[A]): Boolean = false | |||
} | |||
|
|||
/** | |||
* Witness for: Id[A] <-> Unit => A |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we could add something similar for Eval[A]
potentially.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1 , Since this has been blocking 1.2.0 release, I propose we tackle that in a separate PR
I added an issue #2320
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
I have been working on adding representable functors to cats.
This PR adds;
Representable
typeclass.RepresentableStore
data type, which is a generalisation of theStore
comonad.