{"id":16538338,"url":"https://github.com/japgolly/univeq","last_synced_at":"2025-10-29T01:31:30.854Z","repository":{"id":6880583,"uuid":"55467320","full_name":"japgolly/univeq","owner":"japgolly","description":"Safer universal equivalence (==) for Scala.","archived":false,"fork":false,"pushed_at":"2024-08-12T21:41:26.000Z","size":286,"stargazers_count":59,"open_issues_count":14,"forks_count":4,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-02-06T10:11:07.499Z","etag":null,"topics":["equality","equals","scala","scalajs","type-safe","type-safety"],"latest_commit_sha":null,"homepage":"","language":"Scala","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/japgolly.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":".github/FUNDING.yml","license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"patreon":"japgolly"}},"created_at":"2016-04-05T04:22:20.000Z","updated_at":"2024-03-17T22:43:52.000Z","dependencies_parsed_at":"2024-10-27T11:10:14.548Z","dependency_job_id":"bdaad595-356b-49e0-88ef-389b630e7234","html_url":"https://github.com/japgolly/univeq","commit_stats":null,"previous_names":[],"tags_count":26,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/japgolly%2Funiveq","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/japgolly%2Funiveq/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/japgolly%2Funiveq/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/japgolly%2Funiveq/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/japgolly","download_url":"https://codeload.github.com/japgolly/univeq/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":238754216,"owners_count":19524904,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["equality","equals","scala","scalajs","type-safe","type-safety"],"created_at":"2024-10-11T18:45:03.101Z","updated_at":"2025-10-29T01:31:30.310Z","avatar_url":"https://github.com/japgolly.png","language":"Scala","readme":"# UnivEq\n\nSafer universal equivalence for Scala \u0026 Scala.JS.\n*(zero-dependency)*\n\n*Created: Feb 2015.\n\u003cbr\u003eOpen-Sourced: Apr 2016.*\n\n\n## Motivation\n\nIn Scala, all values and objects have the following methods:\n* `equals(Any): Boolean`\n* `==(Any): Boolean`\n* `!=(Any): Boolean`\n\nThis means that you can perform nonsensical comparisons that, at compile-time, you know will fail.\n\nYou're likely to quickly detect this kind of errors when you're writing them for the first time, but the larger problems are:\n* valid comparisons becoming invalid after refactoring your data.\n* 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).\n\nIt's a breeding ground for bugs.\n\n\n## But Scalactic/Scalaz/Cats/X already has an `Equal` class\n\nThis isn't a replacement for the typical `Equal` typeclass you find in other libraries.\nThose define methods of equality, where is this provides a proof that the underlying types' `.equals(Any): Boolean` implementation correctly defines the equality.\nFor example, in a project of mine, I use `UnivEq` for about 95% of data and `scalaz.Equal` for the remaining 5%.\n\nWhy distinguish? Knowing that universal quality holds is a useful property in its own right.\nIt 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.\nSecondly 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`).\n\n\n## Provided Here\nThis library contains:\n\n* A typeclass `UnivEq[A]`.\n* A macro to derive instances for your types.\n* Compilation error if a future change to your data types' args or their types, lose universal equality.\n* Proofs for most built-in Scala \u0026 Java types.\n* Ops `==*`/`!=*` to be used instead of `==`/`!=` so that incorrect type comparison yields compilation error.\n* A few helper methods that provide safety during construction of maps and sets.\n* Optional modules for Scalaz and Cats.\n\n\n## Example\n\n```scala\nimport japgolly.univeq._\n\ncase class Foo[A](name: String, value: Option[A])\n\n// This will fail at compile-time.\n// It doesn't hold for all A...\n//implicit def fooUnivEq[A]: UnivEq[Foo[A]] = UnivEq.derive\n\n// ...It only holds when A has universal equivalence.\nimplicit def fooUnivEq[A: UnivEq]: UnivEq[Foo[A]] = UnivEq.derive\n\n// Let's create data with \u0026 without universal equivalence\ntrait Whatever\nval nope = Foo(\"nope\", Some(new Whatever{}))\nval good = Foo(\"yay\", Some(123))\n\nnope ==* nope // This will fail at compile-time.\nnope ==* good // This will fail at compile-time.\ngood ==* good // This is ok.\n\n// Similarly, if you made a function like:\ndef countUnique[A: UnivEq](as: A*): Int =\n  as.toSet.size\n\ncountUnique(nope, nope) // This will fail at compile-time.\ncountUnique(good, good) // This is ok.\n```\n\n\n## Installation\n\n##### No dependencies:\n```scala\n// Your SBT\nlibraryDependencies += \"com.github.japgolly.univeq\" %%% \"univeq\" % \"1.5.0\"\n// Your code\nimport japgolly.univeq._\n```\n\n##### Cats:\n```scala\n// Your SBT\nlibraryDependencies += \"com.github.japgolly.univeq\" %%% \"univeq-cats\" % \"1.3.0\"\n// Your code\nimport japgolly.univeq.UnivEqCats._\n```\n\n\n## Usage\n\n* Create instances for your own types like this:\n  ```scala\n  implicit def xxxxxxUnivEq[A: UnivEq]: UnivEq[Xxxxxx[A]] = UnivEq.derive\n  ```\n\n* Change `UnivEq.derive` to `UnivEq.deriveDebug` to display derivation details.\n* If needed, you can create instances with `UnivEq.force` to tell the compiler to take your word.\n* Use `==*`/`!=*` in place of `==`/`!=`.\n* Add `: UnivEq` to type params that need it.\n\n\n## Future Work\n\n* Get rid of the `==*`/`!=*`; write a compiler plugin that checks for `UnivEq` at each `==`/`!=`.\n* Add a separate `HashCode` typeclass instead of just using `UnivEq` for maps, sets and similar.\n\nNote: I'm not working on these at the moment, but they'd be fantastic contributions.\n\n## Support\n\nIf you like what I do\n—my OSS libraries, my contributions to other OSS libs, [my programming blog](https://japgolly.blogspot.com)—\nand you'd like to support me, more content, more lib maintenance, [please become a patron](https://www.patreon.com/japgolly)!\nI do all my OSS work unpaid so showing your support will make a big difference.\n","funding_links":["https://patreon.com/japgolly","https://www.patreon.com/japgolly)!"],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjapgolly%2Funiveq","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjapgolly%2Funiveq","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjapgolly%2Funiveq/lists"}