-
-
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 Is constructor for Leibniz equality #1178
Conversation
core/src/main/scala/cats/eq/Is.scala
Outdated
* implicit conversion to the coercion function. | ||
*/ | ||
implicit def witness[A, B](t: A Is B): A => B = | ||
t.subst[A => ?](identity) |
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.
Hm I don't know about this one as it might bring in implicit conversions that may be undesired.. perhaps turn this into a method on the type class itself?
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.
Sounds good. I'll update this presently.
Minor formatting nit: can you reformat your comments like this? /**
* We prefer the Java-style comment syntax.
*/ |
Formatting fixed! 👍 |
Some more questions:
|
Codecov Report
@@ Coverage Diff @@
## master #1178 +/- ##
==========================================
- Coverage 92.49% 92.33% -0.17%
==========================================
Files 246 247 +1
Lines 3891 3900 +9
Branches 136 137 +1
==========================================
+ Hits 3599 3601 +2
- Misses 292 299 +7
Continue to review full report at Codecov.
|
Thanks very much @tel, I had a discussion with @milessabin at gitter IRT testing such code in cats |
This looks good to me. Thanks! One question: how do we feel about a |
@kailuowang Great, thanks! I'll take a look and make some plans to investigate this again perhaps this weekend. @non I immediately agree |
Just to copy some of Miles' comments into this thread:
|
@tel great! thanks for tolerating my bike shed commentary ;) |
core/src/main/scala/cats/eq/Is.scala
Outdated
* equalities to be substituted into many more complex type expressions. | ||
*/ | ||
abstract class Is[A, B] { | ||
def subst[F[_]](fa: F[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.
bikeshed: could we call this substitute
or something? I don't think it is worth saving a few chars personally.
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'm fine with that personally. I defer to Cats House Style whatever that is.
Can we have a simple test just to exercise a few things here:
|
@johnynek I think a good spec around what allocations are acceptable (if any) is good. I added some todo items before landing including working with those. That said, I don't understand what you're trying to say with your example test. Could you rephrase? |
@johnynek I also changed the docs on the class—let me know if you feel that's any better and, as necessary, how I can keep improving it. |
core/src/main/scala/cats/eq/Is.scala
Outdated
def subst[F[_]](fa: F[A]): F[A] = fa | ||
} | ||
implicit def refl[A]: A Is A = | ||
reflAny.asInstanceOf |
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 scala may infer Nothing
as the type parameter here and you may need to instead write: reflAny.asInstanceOf[A Is 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! Fixed!
On Tue, Jul 12, 2016 at 12:52 PM P. Oscar Boykin [email protected]
wrote:
In core/src/main/scala/cats/eq/Is.scala
#1178 (comment):*/
- implicit def refl[A]: A Is A = new Is[A, A] {
- def subst[F[_]](fa: F[A]): F[A] = fa
- }
- implicit def refl[A]: A Is A =
- reflAny.asInstanceOf
I think scala may infer Nothing as the type parameter here and you may
need to instead write: reflAny.asInstanceOf[A Is A]—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/typelevel/cats/pull/1178/files/5558bb1d0d0c37e067f4efdc2a8e8b8debd63690..7a9aa14fa80f0448609b869d228a35d0a6c746b1#r70476249,
or mute the thread
https://github.com/notifications/unsubscribe/AAAEdbdm8tLOyt-yPFKkeYP6Um95z7Lkks5qU8Y9gaJpZM4JEy9z
.
What about adding this method to object: def fromScala[A, B](implicit ev: A =:= B): A Is B =
reflAny.asInstanceOf[A Is B] I can't see how this cast is unsafe, though it is a bit ugly. Can you think of reasons (aside from casting distaste) not to add this? It cannot be unsound, right? The reason I want this, I do sometimes have |
@inline final def compose[C](prev: C Is A): C Is B = | ||
prev andThen this | ||
|
||
@inline final def from: B Is 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.
what about calling this reverse
?
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'm not a huge fan of from
, but I was taking it from some other sources. Calling it reverse
feels alright since underlying A Is B
is an actual process from A
to B
, but I sort of want to wipe that notion away. It's incidental instead of important. So, in order of my preference I like:
flip
mirror
reverse
from
But any of the above are acceptable to me.
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 like flip
.
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.
Just changed it actually!
On Tue, Jul 12, 2016 at 7:06 PM P. Oscar Boykin [email protected]
wrote:
In core/src/main/scala/cats/evidence/Is.scala
#1178 (comment):
- * contexts. This can be a more powerful assertion than
A =:= B
and is more- * easily used in manipulation of types while avoiding (potentially
- * erroneous) coercions.
- *
A Is B
is also known as Leibniz equality.- */
+abstract class Is[A, B] {- def subst[F[_]](fa: F[A]): F[B]
- @inline final def compose[C](prev: C Is A): C Is B =
- prev andThen this
- @inline final def from: B Is A =
I like flip.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/typelevel/cats/pull/1178/files/75d99c44800c0d152e7a093819b87af371f75003#r70538890,
or mute the thread
https://github.com/notifications/unsubscribe/AAAEdWW9A5h087h9IzJYCnKtQC0KWGYEks5qVB4CgaJpZM4JEy9z
.
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.
Just renamed it!
Another question with respect to performance: I've been recently sort of liberally decorating things with |
What do you think of these: // A <: B
sealed abstract class Sub[A, +B] {
def co[F[+_]](f: F[A]): F[B]
def contra[F[-_]](f: F[B]): F[A]
def andThen[B1 >: B, C](that: Sub[B1, C]): Sub[A, C] =
that.co[({type G[+T]=Sub[A, T]})#G](this)
}
object Sub {
implicit def reflS[B, A <: B]: Sub[A, B] = new Sub[A, B] {
def co[F[+_]](f: F[A]): F[B] = f
def contra[F[-_]](f: F[B]): F[A] = f
}
def toSuper[A, B](s: Sub[A, B]): Super[B, A] =
s.contra[({type G[-T]=Super[B, T]})#G](Super.reflSuper[B, B])
}
// A >: B
sealed abstract class Super[A, -B] {
def contra[F[-_]](f: F[A]): F[B]
def co[F[+_]](f: F[B]): F[A]
def andThen[B1 <: B, C](that: Super[B1, C]): Super[A, C] =
that.contra[({type G[-T]=Super[A, T]})#G](this)
}
object Super {
implicit def reflSuper[B, A >: B]: Super[A, B] = new Super[A, B] {
def contra[F[-_]](f: F[A]): F[B] = f
def co[F[+_]](f: F[B]): F[A] = f
}
def toSub[A, B](s: Super[A, B]): Sub[B, A] =
s.co[({type G[+T]=Sub[B, T]})#G](Sub.reflS[B, B])
} which seem like the analogous versions of this for subtyping/supertyping. |
There's Here's a simplified version: abstract class IsA[-A, +B] {
def substitute[F[-_]](fb: F[B]): F[A]
}
object Sub {
// principal basis
def isa[A, B >: A]: A IsA B = new Isa[A, B] {
def substitute[F[-_]](fb: F[B]) = F[A]
}
} Generally I'm not familiar enough with subtyping in a language like Scala to talk about the value of |
I hear you. For My vote in this case is to keep PS: I since opened this PR to scala: which I hope to have merged in 2.13, so this distinction with |
My vote is that we use both, and alias one to the other. I don't think there's a reason to forbid or force people to use either name, and unlike some other types that we have chosen not to do this for, there is literally only one instance with one method so I don't think there is a particularly high documentation burden created by supporting both. |
Wow, re-reading my previous comment, talk about terrible grammar. Guess that's when happens when I attempt to reply to things while amped on coffee.
That's an interesting question, I'll have to think about that some more. I know S11 has written extensively on related topics:
Not surprisingly my vote would be to use |
@tel Would you be willing to add a type alias in package cats
package object evidence {
type Leibniz[A, B] = cats.evidence.Is[A, B]
} |
@non Can do. In terms of moving to merger, should I rebase this patch to the mainline as well? |
Yes, I think so, thanks. Also, you might want to create a single test that exercises this to make the codecov warnings pass. Something like: test("Is / Leibniz") {
def cast1[A, B](as: List[A])(implicit ev: A Is B): List[B] = ev.substitute(as)
cast1[Int, Int](1 :: 2 :: 3 :: Nil)
def cast2[A, B](as: List[A])(implicit ev: Leibniz[A, B]): List[B] = ev.substitute(as)
cast2[Int, Int](1 :: 2 :: 3 :: Nil)
} (Just compiling successfully is as much of a test as we need, and will ensure those lines are covered.) Thanks for sticking with this! |
LGTM 👍 (We can add more coverage tests at some point but this has been hanging out for awhile and I think it's worth merging soon.) |
🎸 |
(See #1177 for initial discussion)
This PR adds Leibniz type equality under the name
A Is B
. Leibniz equality is an improvement overPredef.=:=
as it can be substituted into type expressions and as it has more computational meaning. Both of these add power and convenience toIs
which makes it appealing for certain kinds of pure functional work. I am, for instance, using a local version of this in a Cats-based lens library I'm working on to implement a generalizedEquality[S, T, A, B]
optic at the base of the optical hierarchy. It also shows up nicely for making GADTs with more computational integrity.This is my first Cats contribution so there's probably a lot missing—please let me know how to continue to develop it.
Questions
Is
a good name? Also possible areTEq
,Leib
, andLeibniz
. I likeIs
since it's very direct.Before we can land
cats.eq
tocats.evidence
def fromPredef[A, B](eq: A =:= B): A Is B
illtyped
macrofrom
({from
,reverse
,flip
,mirror
,symm
,inverse
})@inline
designImplicits spec
refl
is available whenever there is an implicit need forA Is A
without extra imports