All Projects → japgolly → Univeq

japgolly / Univeq

Licence: apache-2.0
Safer universal equivalence (==) for Scala.

Programming Languages

scala
5932 projects
scalajs
39 projects

Projects that are alternatives of or similar to Univeq

Scalacss
Super type-safe CSS for Scala and Scala.JS.
Stars: ✭ 293 (+432.73%)
Mutual labels:  type-safety, type-safe
Structopt
Parse command line arguments by defining a struct
Stars: ✭ 323 (+487.27%)
Mutual labels:  type-safety, type-safe
Vuex Smart Module
Type safe Vuex module with powerful module features
Stars: ✭ 306 (+456.36%)
Mutual labels:  type-safety
Type safe
Zero overhead utilities for preventing bugs at compile time
Stars: ✭ 866 (+1474.55%)
Mutual labels:  type-safety
Nexus Plugin Prisma
A plugin for Nexus that integrates Prisma
Stars: ✭ 728 (+1223.64%)
Mutual labels:  type-safety
Graphaello
A Tool for Writing Declarative, Type-Safe and Data-Driven Applications in SwiftUI using GraphQL
Stars: ✭ 355 (+545.45%)
Mutual labels:  type-safety
Swift Tagged
🏷 A wrapper type for safer, expressive code.
Stars: ✭ 801 (+1356.36%)
Mutual labels:  type-safety
Mammoth
A type-safe Postgres query builder for TypeScript.
Stars: ✭ 305 (+454.55%)
Mutual labels:  type-safety
Typewiz
Automatically discover and add missing types in your TypeScript code
Stars: ✭ 1,026 (+1765.45%)
Mutual labels:  type-safety
Gentype
Auto generation of idiomatic bindings between Reason and JavaScript: either vanilla or typed with TypeScript/FlowType.
Stars: ✭ 683 (+1141.82%)
Mutual labels:  type-safety
Pcf Nominal
A formalisation of PCF in Agda using naive nominal binding
Stars: ✭ 5 (-90.91%)
Mutual labels:  type-safety
Swift Nonempty
🎁 A compile-time guarantee that a collection contains a value.
Stars: ✭ 585 (+963.64%)
Mutual labels:  type-safety
Flow Runtime
A runtime type system for JavaScript with full Flow compatibility.
Stars: ✭ 813 (+1378.18%)
Mutual labels:  type-safety
Jni.hpp
A modern, type-safe, header-only, C++14 wrapper for JNI
Stars: ✭ 313 (+469.09%)
Mutual labels:  type-safety
Zion
A statically-typed strictly-evaluated garbage-collected readable programming language.
Stars: ✭ 33 (-40%)
Mutual labels:  type-safety
Caramel
🍬 a functional language for building type-safe, scalable, and maintainable applications
Stars: ✭ 756 (+1274.55%)
Mutual labels:  type-safe
Gremlin Scala
Scala wrapper for Apache TinkerPop 3 Graph DSL
Stars: ✭ 462 (+740%)
Mutual labels:  type-safe
Magic Type
🎩 Use Your TypeScript definitions at runtime. Powered by Manta Style.
Stars: ✭ 53 (-3.64%)
Mutual labels:  type-safety
Phpcs Type Sniff
PHP CodeSniffer sniff to enforce PHP 7 types and documentation of array variables
Stars: ✭ 35 (-36.36%)
Mutual labels:  type-safety
Xrayinterface
Convenient access to private members of Java classes (Java 8)
Stars: ✭ 5 (-90.91%)
Mutual labels:  type-safe

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 vals. 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

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.size

countUnique(nope, nope) // This will fail at compile-time.
countUnique(good, good) // This is ok.

Installation

No dependencies:
// Your SBT
libraryDependencies += "com.github.japgolly.univeq" %%% "univeq" % "1.2.0"
// Your code
import japgolly.univeq._
Scalaz:
// Your SBT
libraryDependencies += "com.github.japgolly.univeq" %%% "univeq-scalaz" % "1.2.0"
// Your code
import japgolly.univeq.UnivEqScalaz._
Cats:
// Your SBT
libraryDependencies += "com.github.japgolly.univeq" %%% "univeq-cats" % "1.2.0"
// Your code
import japgolly.univeq.UnivEqCats._

Usage

  • Create instances for your own types like this:

    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— and you'd like to support me, more content, more lib maintenance, please become a patron! I do all my OSS work unpaid so showing your support will make a big difference.

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].