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

Add Comparison trait. #24

Merged
merged 11 commits into from
Apr 8, 2024
Merged

Conversation

zainab-ali
Copy link
Contributor

@zainab-ali zainab-ali commented Apr 3, 2024

This PR adds a Comparison trait as referenced in #15 .

It still needs docs and tests, but I want to discuss the API before moving on to them.

@zainab-ali zainab-ali marked this pull request as draft April 3, 2024 16:21
loc: SourceLocation): Expectations = eql(expected, found)
loc: SourceLocation): Expectations = eql(expected, found)(eqA, showA, loc)

def diff[A](
Copy link
Contributor Author

Choose a reason for hiding this comment

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

What do you think about adding a new function vs modifying the existing ones?

Adding a new function to the API (diff or a different name) is the simplest approach to compatibility.

We could modify the signatures of the existing expect and same functions to replace Eq and Show with Comparison, but it would be difficult to keep the same behaviour. They currently use default implicit parameters to infer the Show and Eq traits.

The closest we could get is:

object Comparison {
  implicit def fromEqAndToString(
      implicit val eq: Eq[A],
      show[A]: Show[A] = Show.fromToString): Comparison[A] = ???
}
def eql[A](
    expected: A,
    found: A)(
    implicit comparison: Comparison[A], // finds Comparison.fromEqAndToString
    loc: SourceLocation): Expectations = ???

def same[A](
    expected: A,
    found: A)(
    implicit comparison: Comparison[A] = Comparison.fromUniversalEqAndToString,
    loc: SourceLocation): Expectations = ???

but same wouldn't respect a custom Show instance.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Repeating the discord convo for posterity : I'm fine the suggestion, despite it breaking bincompat and behaviour on this.

import cats.Show
import com.eed3si9n.expecty._

trait Comparison[A] {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This could potentially extend Eq as a Comparison instance does imply an Eq instance (and should satisfy Eq laws).

I'm not sure if the inheritance would play well with implicit searches.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Interesting question.

I think Comparison should not necessarily imply Eq, because you could have comparisons that verify if an instance A is contained in another A and I think that'd be a legitimate use. However there could be a more refined EqComparison that would imply Eq. In that case, the MTL treatment of having the implication reified as a def eqInstance: Eq[A] instead of OO-inheritance would probably be the right course of action, in order to avoid implicit search problems.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That's an interesting case. In that vein, Comparison is a general predicate that has a string representation on failure? It doesn't have any laws associated with it.

Copy link
Collaborator

Choose a reason for hiding this comment

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

In that vein, Comparison is a general predicate that has a string representation on failure? It doesn't have any laws associated with it.

I think so, yes

import com.eed3si9n.expecty._

trait Comparison[A] {
def diff(expected: A, found: A): Option[String]
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This result type could be more explicit, if we wanted:

sealed trait Result
object Result {
   case object Success extends Result
   case object Failure(diff: String) extends Result
}

Copy link
Collaborator

Choose a reason for hiding this comment

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

I think that's a good idea. There should however be a couple builders in the companion object, for convenience :

def ???[A](f : (A, A) => Option[String]) : Comparison[A] 
// and maybe : 
def ???[A](f: PartialFunction[(A, A), String]) : Comparison[A] 

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I went with instance to align with Eq.

@zainab-ali zainab-ali marked this pull request as ready for review April 4, 2024 16:03
implicit eqA: Eq[A] = Eq.fromUniversalEquals[A],
showA: Show[A] = Show.fromToString[A],
loc: SourceLocation): Expectations = eql(expected, found)
implicit loc: SourceLocation): Expectations = eql(expected, found)(
Copy link
Collaborator

Choose a reason for hiding this comment

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

Mmm what if we still passed the comparison as an implicit parameter with the default ? Now that I think a bit more about it, the default instance was the main motivation for having a distinction in the previous version between same and eql, whilst still allowing for injectable behaviour. Retaining this property would be valuable I think

implicit comparisonA: Comparison[A] = Comparison.fromEq(Eq.fromUniversalEquals, Show.fromToString)

@Baccata Baccata merged commit b4f318a into typelevel:main Apr 8, 2024
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants