Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/japgolly/univeq
Safer universal equivalence (==) for Scala.
https://github.com/japgolly/univeq
equality equals scala scalajs type-safe type-safety
Last synced: about 21 hours ago
JSON representation
Safer universal equivalence (==) for Scala.
- Host: GitHub
- URL: https://github.com/japgolly/univeq
- Owner: japgolly
- License: apache-2.0
- Created: 2016-04-05T04:22:20.000Z (almost 9 years ago)
- Default Branch: master
- Last Pushed: 2024-08-12T21:41:26.000Z (5 months ago)
- Last Synced: 2025-01-14T14:09:37.700Z (8 days ago)
- Topics: equality, equals, scala, scalajs, type-safe, type-safety
- Language: Scala
- Homepage:
- Size: 279 KB
- Stars: 59
- Watchers: 3
- Forks: 4
- Open Issues: 14
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- Funding: .github/FUNDING.yml
Awesome Lists containing this project
README
# UnivEq
Safer universal equivalence for Scala & Scala.JS.
*(zero-dependency)**Created: Feb 2015.
Open-Sourced: Apr 2016.*## Motivation
In Scala, all values and objects have the following methods:
* `equals(Any): Boolean`
* `==(Any): Boolean`
* `!=(Any): Boolean`This means that you can perform nonsensical comparisons that, at compile-time, you know will fail.
You're likely to quickly detect this kind of errors when you're writing them for the first time, but the larger problems are:
* valid comparisons becoming invalid after refactoring your data.
* calling a method that expects universal equality to hold with a data type in which it doesn't (eg. a method that uses `Set` under the hood).It's a breeding ground for bugs.
## But Scalactic/Scalaz/Cats/X already has an `Equal` class
This isn't a replacement for the typical `Equal` typeclass you find in other libraries.
Those define methods of equality, where is this provides a proof that the underlying types' `.equals(Any): Boolean` implementation correctly defines the equality.
For example, in a project of mine, I use `UnivEq` for about 95% of data and `scalaz.Equal` for the remaining 5%.Why distinguish? Knowing that universal quality holds is a useful property in its own right.
It means a more efficient equals implementation because typeclass instances *aren't used* for comparison, which means they're dead code and can be optimised away along with their construction if `def` or `lazy val`s.
Secondly 99.99% of classes with sensible `.equals` also have sensible `.hashCode` implementations which means it's a good constraint to apply to methods that will depend on it (eg. if you call `.toSet`).## Provided Here
This library contains:* A typeclass `UnivEq[A]`.
* A macro to derive instances for your types.
* Compilation error if a future change to your data types' args or their types, lose universal equality.
* Proofs for most built-in Scala & Java types.
* Ops `==*`/`!=*` to be used instead of `==`/`!=` so that incorrect type comparison yields compilation error.
* A few helper methods that provide safety during construction of maps and sets.
* Optional modules for Scalaz and Cats.## Example
```scala
import japgolly.univeq._case class Foo[A](name: String, value: Option[A])
// This will fail at compile-time.
// It doesn't hold for all A...
//implicit def fooUnivEq[A]: UnivEq[Foo[A]] = UnivEq.derive// ...It only holds when A has universal equivalence.
implicit def fooUnivEq[A: UnivEq]: UnivEq[Foo[A]] = UnivEq.derive// Let's create data with & without universal equivalence
trait Whatever
val nope = Foo("nope", Some(new Whatever{}))
val good = Foo("yay", Some(123))nope ==* nope // This will fail at compile-time.
nope ==* good // This will fail at compile-time.
good ==* good // This is ok.// Similarly, if you made a function like:
def countUnique[A: UnivEq](as: A*): Int =
as.toSet.sizecountUnique(nope, nope) // This will fail at compile-time.
countUnique(good, good) // This is ok.
```## Installation
##### No dependencies:
```scala
// Your SBT
libraryDependencies += "com.github.japgolly.univeq" %%% "univeq" % "1.5.0"
// Your code
import japgolly.univeq._
```##### Cats:
```scala
// Your SBT
libraryDependencies += "com.github.japgolly.univeq" %%% "univeq-cats" % "1.3.0"
// Your code
import japgolly.univeq.UnivEqCats._
```## Usage
* Create instances for your own types like this:
```scala
implicit def xxxxxxUnivEq[A: UnivEq]: UnivEq[Xxxxxx[A]] = UnivEq.derive
```* Change `UnivEq.derive` to `UnivEq.deriveDebug` to display derivation details.
* If needed, you can create instances with `UnivEq.force` to tell the compiler to take your word.
* Use `==*`/`!=*` in place of `==`/`!=`.
* Add `: UnivEq` to type params that need it.## Future Work
* Get rid of the `==*`/`!=*`; write a compiler plugin that checks for `UnivEq` at each `==`/`!=`.
* Add a separate `HashCode` typeclass instead of just using `UnivEq` for maps, sets and similar.Note: I'm not working on these at the moment, but they'd be fantastic contributions.
## Support
If you like what I do
—my OSS libraries, my contributions to other OSS libs, [my programming blog](https://japgolly.blogspot.com)—
and you'd like to support me, more content, more lib maintenance, [please become a patron](https://www.patreon.com/japgolly)!
I do all my OSS work unpaid so showing your support will make a big difference.