-
-
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
Merged
Merged
Changes from all commits
Commits
Show all changes
17 commits
Select commit
Hold shift + click to select a range
9481eed
Add Is constructor for Leibniz equality
tel 705900d
Remove Id witness implicit favoring explicit conversion
tel 11dacb5
Fix comment formatting
tel 07d1a60
Add newline at end of file to pass style checks
tel 374d0bb
better documentation and inlinability
tel 27f7df6
refactor cats.eq to cats.evidence
tel c3a66ca
explicit type specialization for reflAny cast
tel 1a8f2aa
make Is extend Serializable
tel 7dd4f9a
convert implicit def predefEq to a Is method
tel a93db93
add unsafeFromPredef for consideration
tel 36f58fa
rename subst to substitute and add docs
tel 533cb36
fix to javadoc style comment doc for unsafeToPredef
tel 673d3cd
liberally decorate with @inline
tel 7ce238d
add some more documentation of Is methods
tel e7b3ea6
fix document formatting to prevent formatting errors
tel 65b8811
Add Leibniz alias for cats.evidence.Is
tel 78960e2
Add test for Leibniz / Is; check only
tel File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
package cats.evidence | ||
|
||
import cats.Id | ||
|
||
/** | ||
* A value of `A Is B` is proof that the types `A` and `B` are the same. More | ||
* powerfully, it asserts that they have the same meaning in all type | ||
* 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] extends Serializable { | ||
|
||
/** | ||
* To create an instance of `A Is B` you must show that for every choice | ||
* of `F[_]` you can convert `F[A]` to `F[B]`. Loosely, this reads as | ||
* saying that `B` must have the same effect as `A` in all contexts | ||
* therefore allowing type substitution. | ||
*/ | ||
def substitute[F[_]](fa: F[A]): F[B] | ||
|
||
/** | ||
* `Is` is transitive and therefore values of `Is` can be composed in a | ||
* chain much like functions. See also `compose`. | ||
*/ | ||
@inline final def andThen[C](next: B Is C): A Is C = | ||
next.substitute[A Is ?](this) | ||
|
||
/** | ||
* `Is` is transitive and therefore values of `Is` can be composed in a | ||
* chain much like functions. See also `andThen`. | ||
*/ | ||
@inline final def compose[C](prev: C Is A): C Is B = | ||
prev andThen this | ||
|
||
/** | ||
* `Is` is symmetric and therefore can be flipped around. Flipping is its | ||
* own inverse, so `x.flip.flip == x`. | ||
*/ | ||
@inline final def flip: B Is A = | ||
this.substitute[? Is A](Is.refl) | ||
|
||
/** | ||
* Sometimes for more complex substitutions it helps the typechecker to | ||
* wrap one layer of `F[_]` context around the types you're equating | ||
* before substitution. | ||
*/ | ||
@inline final def lift[F[_]]: F[A] Is F[B] = | ||
substitute[λ[α => F[A] Is F[α]]](Is.refl) | ||
|
||
/** | ||
* Substitution on identity brings about a direct coercion function of the | ||
* same form that `=:=` provides. | ||
*/ | ||
@inline final def coerce(a: A): B = | ||
substitute[Id](a) | ||
|
||
/** | ||
* A value `A Is B` is always sufficient to produce a similar `Predef.=:=` | ||
* value. | ||
*/ | ||
@inline final def predefEq: A =:= B = | ||
substitute[A =:= ?](implicitly[A =:= A]) | ||
} | ||
|
||
object Is { | ||
|
||
/** | ||
* In truth, "all values of `A Is B` are `refl`". `reflAny` is that | ||
* single value. | ||
*/ | ||
private[this] val reflAny = new Is[Any, Any] { | ||
def substitute[F[_]](fa: F[Any]) = fa | ||
} | ||
|
||
/** | ||
* In normal circumstances the only `Is` value which is available is the | ||
* computational identity `A Is A` at all types `A`. These "self loops" | ||
* generate all of the behavior of equality and also ensure that at its | ||
* heart `A Is B` is always just an identity relation. | ||
* | ||
* Implementation note: all values of `refl` return the same (private) | ||
* instance at whatever type is appropriate to save on allocations. | ||
*/ | ||
@inline implicit def refl[A]: A Is A = | ||
reflAny.asInstanceOf[A Is A] | ||
|
||
/** | ||
* It can be convenient to convert a `Predef.=:=` value into an `Is` value. | ||
* This is not strictly valid as while it is almost certainly true that | ||
* `A =:= B` implies `A Is B` it is not the case that you can create | ||
* evidence of `A Is B` except via a coercion. Use responsibly. | ||
*/ | ||
@inline def unsafeFromPredef[A, B](eq: A =:= B): A Is B = | ||
reflAny.asInstanceOf[A Is B] | ||
|
||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
package cats | ||
|
||
package object evidence { | ||
type Leibniz[A, B] = cats.evidence.Is[A, B] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
package cats.tests | ||
|
||
import cats.evidence._ | ||
|
||
class EvidenceTests extends CatsSuite { | ||
|
||
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) | ||
|
||
} | ||
|
||
} |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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
Is
is a better name as a replacement of<:<
, not=:=
.Did you think about the other names, like
Same
, for this type class?