Skip to content

Instantly share code, notes, and snippets.

@MateuszKubuszok
Last active July 17, 2025 02:27
Show Gist options
  • Save MateuszKubuszok/318d60d4116db95ac03a2a3e112e1751 to your computer and use it in GitHub Desktop.
Save MateuszKubuszok/318d60d4116db95ac03a2a3e112e1751 to your computer and use it in GitHub Desktop.
Newtypes and tagged types

Tagged, refined and newtypes for Scala

Because we have too few solutions

Library Docs Types Integrations Notes
built-in official AnyVal extended by class with a single value many libraries support AnyVals - Scala 2/3, but on 3 it has a replacement
- cannot always prevent boxing (unreliable)
built-in official opaque type Name = Underlying vanilla version has no integrations - Scala 3-only
- always prevents boxing
Scalaz (scalaz/scalaz) Underlying @@ Tag ? - Scala 2/3
- it's a hack but works
Macwire (softwaremill/macwire) Underlying @@ Tag ? - Scala 2/3
- like Scalaz' version
- only used for DI with Macwire
Kebs (theiterators/kebs) Underlying @@ Tag
@tagged macro annotation
Opaque[Underlying, Name] extended by Name companion
a lot - Scala 2/3
- solution for Scala 2/3 and integrations
NewType (estatico/scala-newtype) @newtype, @newsubtype macro annotations a lot, via Coercible[Underlying, Name] type class - Scala 2-only (macro-annotations on Scala 3 are a blocker)
- a lof of integrations
- macro annotation conveniently rewrites methods into extension methods
ZIO Prelude (zio/zio-prelude) official Newtype[Underlying], Subtype[Underlying] - extended by companion objects ??? - Scala 2/3
- requires manually defining extension methods in companion object
supertagged for scala (rudogma/scala-supertagged) Underlying @@ Tag
Newtype0, NewtypeT[Underlying], Newtype[Underlying] and TaggedType variants - extended by companion objects
possible, via Extractor[Underlying, Name] type class - Scala 2-only
- requires manually defining extension methods in companion object
Monix’s Newtypes (monix/newtypes) official Newtype[Underlying], NewtypeWrapped[Underlying], NewtypeValidated[Underlying], NewtypeK[Underlying], NewtypeCovariantK[Underlying] and NewSubtype[Underlying] variants - extended by companion objects possible, via HasExtractor.Aux[Name, Underlying] and HasBuilder.Aux[Name, Underlying] type classes - Scala 2/3
- requires manually defining extension methods in companion object
neotype (kitlangton/neotype) TypeWrapper[Underlying], Newtype[Underlying], Subtype[Underlying] - extended by companion objects some, via WithType[Underlying, Name] type class - Scala 3-only
- allows validation of (some) input in compile-time
- requires manually defining extension methods in companion object
Yet Another Newtype Library - yantl (arturaz/yantl) official Newtype.Of[Underlying], Newtype.ValidatedOf(...), Newtype.WithoutValidationOf[Underlying] extended by companion object possible, via Newtype.WithType or Newtype.WithUnvalidatedType type classes - Scala 3-only
- requires manually defining extension methods in companion object
refined (fthomas/refined) Underlying Refined Refinment a lot, via Validate type class - Scala 2/3
- allows compile-time validation of some values, but only on Scala 2
Iron (Iltotore/iron) official Underlying :| Refinment, RefinedTypeOps[Underlying, Refinement, OpaqueTypeName] extended by companion object a lot via Constraint type class - Scala 3-only
- allows validation of (some) input in compile-time
Refined4s (kevin-lee/refined4s) official Refined[Underlying] , NewType[Underlying] extended by companion object some, via mixins to companion object or deriving method in companion - Scala 2/3

I conveniently skipped detailed description for some other attempts that had few stars like:

Motivation

The motivation of this list is not comparing the features of tagged/newtype/refined libraries - they 90% overlapping.

The goal is to show that instead of picking 1 solution, commiting to it, and making sure all libraries would have good integration with it, we have gazillion of half-baked solutions, none of which is perfect, each boasting about 1 thing that no-one else has, and each requiring integrating it yourself with half the libraries because only half of them have existing integrations that the author just happen to find convenient.

It is a huge waste of duplicated effort, it showcases that development in our community is ego-driven ("there is no glory in contributing to someone else library, but there is in writing it from scratch!"), and this is why we cannot have nice things.

If this comment offended you, good. It means that the message reached.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment