{"id":13682794,"url":"https://github.com/estatico/scala-newtype","last_synced_at":"2025-04-12T22:37:12.304Z","repository":{"id":20577852,"uuid":"90323300","full_name":"estatico/scala-newtype","owner":"estatico","description":"NewTypes for Scala with no runtime overhead","archived":false,"fork":false,"pushed_at":"2022-02-10T08:49:09.000Z","size":159,"stargazers_count":544,"open_issues_count":25,"forks_count":31,"subscribers_count":11,"default_branch":"master","last_synced_at":"2025-04-04T02:07:59.044Z","etag":null,"topics":["newtype","scala","tagged","tagged-types"],"latest_commit_sha":null,"homepage":null,"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/estatico.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-05-05T01:06:55.000Z","updated_at":"2025-01-19T06:27:55.000Z","dependencies_parsed_at":"2022-07-26T09:32:00.438Z","dependency_job_id":null,"html_url":"https://github.com/estatico/scala-newtype","commit_stats":null,"previous_names":[],"tags_count":10,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/estatico%2Fscala-newtype","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/estatico%2Fscala-newtype/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/estatico%2Fscala-newtype/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/estatico%2Fscala-newtype/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/estatico","download_url":"https://codeload.github.com/estatico/scala-newtype/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248643006,"owners_count":21138353,"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":["newtype","scala","tagged","tagged-types"],"created_at":"2024-08-02T13:01:53.304Z","updated_at":"2025-04-12T22:37:12.281Z","avatar_url":"https://github.com/estatico.png","language":"Scala","funding_links":[],"categories":["Scala"],"sub_categories":[],"readme":"# NewType\n\nNewTypes for Scala with no runtime overhead.\n\n[![Build Status](https://travis-ci.org/estatico/scala-newtype.svg?branch=master)](https://travis-ci.org/estatico/scala-newtype)\n[![Gitter](https://img.shields.io/badge/gitter-join%20chat-green.svg)](https://gitter.im/estatico/scala-newtype)\n[![Maven Central](https://img.shields.io/maven-central/v/io.estatico/newtype_2.13.svg)](https://maven-badges.herokuapp.com/maven-central/io.estatico/newtype_2.13)\n\n## Getting NewType\n\nIf you are using SBT, add the following line to your build file -\n\n```scala\nlibraryDependencies += \"io.estatico\" %% \"newtype\" % \"0.4.4\"\n```\n\nMake sure you have [macro-paradise](https://docs.scala-lang.org/overviews/macros/paradise.html) enabled\n - for Scala 2.13.0-M3 and lower add the following line to your build file\n```scala\naddCompilerPlugin(\"org.scalamacros\" % \"paradise\" % \"2.1.1\" cross CrossVersion.full)\n```\n- for Scala 2.13.0-M4 and above via compiler flag [`-Ymacro-annotations`](https://github.com/scala/scala/pull/6606)\n\nFor Maven or other build tools, see the Maven Central badge at the top of this README.\n\n## Usage\n\nFor generating newtypes via the `@newtype` macro, see [@newtype macro](#newtype-macro)\nFor non-macro usage, see the section on [Legacy encoding](#legacy-encoding).\n\n### @newtype macro\n\nAs of newtype 0.2, you can now encode newtypes using the `@newtype` macro. Its implementation\nand usage aligns closely with idiomatic Scala syntax, so IDE support _just works_ out of the box.\n\n```scala\nimport io.estatico.newtype.macros.newtype\n\npackage object types {\n\n  @newtype case class WidgetId(toInt: Int)\n}\n```\n\nThis expands into a `type` and companion `object` definition, so newtypes _must_ be defined\nin an `object` or `package object`.\n\nThe example above will generate code similar to the following -\n\n```scala\npackage object types {\n  type WidgetId = WidgetId.Type\n  object WidgetId {\n    type Repr = Int\n    type Base = Any { type WidgetId$newtype }\n    trait Tag extends Any\n    type Type \u003c: Base with Tag\n\n    def apply(x: Int): WidgetId = x.asInstanceOf[WidgetId]\n\n    implicit final class Ops$newtype(val $this$: Type) extends AnyVal {\n      def toInt: Int = $this$.asInstanceOf[Int]\n    }\n  }\n}\n```\n\nYou can also create newtypes which have type parameters -\n\n```scala\n@newtype case class EitherT[F[_], L, R](x: F[Either[L, R]])\n```\n\nNote that it is impossible to have your newtype _extend_ any types, which\nmakes sense since it has its own distinct type at compile time and at runtime is just\nthe underlying value.\n\nAlso, since the `@newtype` annotation gives your type a distinct type at compile-time,\nprimitives will naturally box as they do when they are applied in any generic context.\nSee the following section on `@newsubtype` for unboxed primitive newtypes.\n\n#### @newsubtype macro\n\nAs of newtype 0.4 you now have access to the `@newsubtype` macro. Its usage is identical\nto `@newtype`. The difference is that it functions as a _subtype_ of the underlying\ntype as opposed to having a completely different type at compile time. This may or may\nnot be desirable, and it's recommended to use `@newtype` if you're not entirely sure you\nactually need `@newsubtype`.\n\nThe difference in the generated code is that `@newsubtype` defines its `Base` type defined as -\n\n```scala\ntype Base = Repr\n```\n\nThe main benefit of `@newsubtype` is that primitives are unboxed. For example, the\nfollowing `@newtype` definition will box the `Int`, making it a `java.lang.Integer`\nat runtime -\n\n```scala\n@newtype case class Foo(x: Int)\n```\n\nHowever, the following `@newsubtype` definition will be a primitive `int` at runtime -\n\n```scala\n@newsubtype case class Bar(x: Int)\n```\n\nNote however that calling `getClass` on a newsubtype will fool you -\n\n```scala\nscala\u003e Bar(1).getClass\nres2: Class[_ \u003c: Bar] = class java.lang.Integer\n```\n\nReason is that scalac boxes unnecessarily when calling `getClass`, see https://github.com/scala/bug/issues/10770\n\nWe can confirm that we do in fact have a primitive `int` at runtime back by inspecting the byte code -\n\n```scala\nscala\u003e class Test { def test = Bar(1) }\nscala\u003e :javap Test\n```\n```java\n...\n  public int test();\n...\n```\n\nAnother \"feature\" of `@newsubtype` is that its values can be passed to functions\nwhich accept its `Repr` type without needing to convert them first -\n\n```scala\nscala\u003e def half(b: Int): Int = b / 2\nscala\u003e half(Bar(12))\nres6: Int = 6\n```\n\nNote that this feature can be undesirable since the newsubtype will be automatically\nunwrapped, even when you might not mean to. Again, unless you have a good reason to use\n`@newsubtype`, it's recommend to use `@newtype` by default.\n\n#### Smart Constructors and Accessor Methods\n\nThis library gives you a few choices when it comes to defining smart constructors\nand accessor methods for your newtypes. Efforts have been made to keep things idiomatic.\nNote that extractors (`unapply` methods) are **not** generated by newtypes.\n\nUsing `case class` gives us a smart constructor (an `apply` method on the companion object)\nthat will accept a value of type `A` and return the newtype `N`.\n\n```scala\n@newtype case class N(a: A)\n```\n\nYou also get an accessor extension method to get the underlying `A`. Note that you can\nprevent this by defining the field as private.\n\n```scala\n@newtype case class N(private val a: A)\n```\n\nUsing `class` will not generate a smart constructor (no `apply` method). This allows\nyou to specify your own. Note that `new` never works for newtypes and will fail to compile.\n\nIf you wish to generate an accessor method for your underlying value, you can define it as `val`\njust as if you were dealing with a normal class.\n\n```scala\n@newtype class N(val a: A)\n```\n\nIf you need to define your own smart constructor, use the\n`.coerce` extension method to cast to your newtype.\n\n```scala\nimport io.estatico.newtype.ops._\n\n@newtype class Id(val strValue: String)\n\nobject Id {\n  def fromString(str: String): Either[String, Id] = {\n    if (str.isEmpty) Left(\"Id cannot be empty\")\n    else Right(str.coerce)\n  }\n}\n```\n\n#### Extension Methods\n\nDefining extension methods are as simple as defining normal methods in any class -\n\n```scala\n@newtype case class OptionT[F[_], A](value: F[Option[A]]) {\n\n  def fold[B](default: =\u003e B)(f: A =\u003e B)(implicit F: Functor[F]): F[B] =\n    F.map(value)(_.fold(default)(f))\n\n  def cata[B](default: =\u003e B, f: A =\u003e B)(implicit F: Functor[F]): F[B] =\n    fold(default)(f)\n\n  def map[B](f: A =\u003e B)(implicit F: Functor[F]): OptionT[F, B] =\n    OptionT(F.map(value)(_.map(f)))\n}\n```\n\n#### Companion Objects\n\nThe companion object works just as you'd expect. You can place your type class instances\nthere and implicit resolution just works.\n\nCompanion objects also contain special `deriving` and `derivingK`\nmethods to auto-derive instances for you if one exists for your underlying type.\nThis is similar to GHC Haskell's `GeneralizedNewtypeDeriving` extension.\n\n`deriving` is used for type classes whose type parameter is _not_ higher kinded.\n\n```scala\n@newtype case class Text(s: String)\nobject Text {\n  implicit val arb: Arbitrary[Text] = deriving\n}\n```\n\n`derivingK` is used for type classes whose type parameter _is_ higher kinded.\n\n```scala\n@newtype class Nel[A](val toList: List[A])\nobject Nel {\n  def apply[A](head: A, tail: List[A]): Nel[A] = (head +: tail).coerce[Nel[A]]\n  implicit val functor: Functor[Nel] = derivingK\n}\n```\n\nNote that since these methods are created by the `@newtype` macro, IDEs will generally\nnot be able to resolve them. If the red highlighting bothers you, you can use\n`.coerce` to safely cast the base type class to support your newtype -\n\n```scala\nimport io.estatico.newtype.ops._\n\n@newtype case class Text(s: String)\nobject Text {\n  implicit val arb: Arbitrary[Text] = implicitly[Arbitrary[String]].coerce\n}\n\n@newtype class Nel[A](val toList: List[A])\nobject Nel {\n  def apply[A](head: A, tail: List[A]): Nel[A] = (head +: tail).coerce[Nel[A]]\n  implicit val functor: Functor[Nel] = implicitly[Functor[List]].coerce\n}\n```\n\n### Coercible Instance Trick\n\n**Note that this is NOT recommended!**\n\nIn some cases, you may want to automatically derive a type class instance\nfor all newtypes by leveraging `Coercible`. While seemingly convenient, this is\n**NOT** recommended as it in some ways goes against the spirit of using\na newtype in the first place. Specializing a specific instance for a\nnewtype will be tricky and will require clever implicit scoping. Also,\nit can [greatly increase your compile times](https://github.com/estatico/scala-newtype/issues/64).\nInstead, it's generally better to explicitly define instances for your\nnewtypes.\n\n**You have been warned!**\n\nThe following example generates an `Eq` instance for all newtypes in which\ntheir underlying `Repr` type has an `Eq` instance.\n\n```scala\nscala\u003e :paste\n\nimport cats._, cats.implicits._\n\n/** If we have an Eq instance for Repr type R, derive an Eq instance for newtype N. */\nimplicit def coercibleEq[R, N](implicit ev: Coercible[Eq[R], Eq[N]], R: Eq[R]): Eq[N] =\n  ev(R)\n\n@newtype case class Foo(x: Int)\n\n// Exiting paste mode, now interpreting.\n\nscala\u003e Foo(1) === Foo(2)\nres0: Boolean = false\n```\n\nHowever, as mentioned, it's generally better to explicitly define your\ninstances.\n\n```scala\n@newtype case class Foo(x: Int)\nobject Foo {\n  implicit val eq: Eq[Foo] = deriving\n}\n```\n\nYou may not always be able to put your instance in the companion object, likely\nbecause the type class is not available where you are defining your newtype.\nIn this case, simply define an orphan instance and import it where you need.\n\n```scala\nobject EqOrphans {\n  implicit val eqFoo: Eq[Foo] = implicitly[Eq[Int]].coerce\n}\n```\n\n### Legacy encoding\n\nIf you don't wish to use the macro API, you can still use the legacy API for building\nnewtypes manually via companion objects. Note that this method does not support newtypes\nwith type parameters. If you need type parameters, use the macro API.\n\nThe easiest way to get going with the legacy encoding is to create an object that extends from\n`NewType.Default` -\n\n```scala\nimport io.estatico.newtype.NewType\n\nobject WidgetId extends NewType.Default[Int]\n```\n\nThis will be the companion object for your newtype. Use the `.Type` type member to get access\nto the type for signatures. A common pattern is to include this in a package object\nso it can be easily imported.\n\n```scala\npackage object types {\n  type WidgetId = WidgetId.Type\n  object WidgetId extends NewType.Default[Int]\n}\n```\n\nNow you can import `types.WidgetId` and use it in type signatures as well as the companion\nobject.\n\n#### `NewType.Of` vs. `NewType.Default`\n\nExtending `NewType.Of` simply creates the newtype wrapper; however, you will often\nwant to extend `NewType.Default` to provide some helper methods on the companion\nobject -\n\n```scala\n// Safely casts an Int to a WidgetId\nscala\u003e WidgetId(1)\nres0: WidgetId.Type = 1\n\n// Safely casts M[Int] to M[WidgetId]\nscala\u003e WidgetId.applyM(List(1, 2))\nres1: List[WidgetId.Type] = List(1, 2)\n```\n\nSee `NewTypeExtras` for the available mixins for creating newtype wrappers.\n\nIf you wish to do something different, you can supply your own smart-constructor\ninstead -\n\n```scala\nobject Nat extends NewType.Of[Int] {\n  def apply(n: Int): Option[Type] = if (n \u003c 0) None else Some(wrap(n))\n}\n```\n\nThe `wrap` method you see here is actually just explicit usage of the\nimplicit instance of `Coercible[Int, Nat.Type]`.\nSee the section on [Coercible](#coercible) for more info.\n\n#### Legacy extension methods\n\nYou probably want to be able to add methods to your newtypes. You can do this using\nScala's extension methods via implicit classes -\n\n```scala\ntype Point = Point.Type\nobject Point extends NewType.Of[(Int, Int)] {\n\n  def apply(x: Int, y: Int): Type = wrap((x, y))\n\n  implicit final class Ops(val self: Type) extends AnyVal {\n    def toTuple: (Int, Int) = unwrap(self)\n    def x: Int = toTuple._1\n    def y: Int = toTuple._2\n  }\n}\n```\n```scala\nscala\u003e val p = Point(1, 2)\np: Point.Type = (1,2)\n\nscala\u003e p.toTuple\nres7: (Int, Int) = (1,2)\n\nscala\u003e p.x\nres8: Int = 1\n\nscala\u003e p.y\nres9: Int = 2\n```\n\n#### Legacy type class instances and implicits\n\nAs mentioned, the object you create via extending one of the `NewType` helpers\nfunctions as the companion object for your newtype. As such, you can leverage this\nfor type class instances to avoid orphan instances -\n\n```scala\nobject Nat extends NewType.Of[Int] {\n  implicit val show: Show[Type] = Show.instance(_.toString)\n}\n```\n\nIf you use `NewType.Default`, you can use the `deriving` method to derive\ntype class instances for those that exist for your newtype's base type.\n\n```scala\nobject Nat extends NewType.Default[Int] {\n  implicit def show: Show[Type] = deriving\n}\n```\n\nAs long as an implicit instance of `Show[Int]` exists in scope, `deriving` will\ncast the instance to one suitable for your newtype. This is similar to GHC Haskell's\n`GeneralizedNewtypeDeriving` extension.\n\n#### Legacy NewSubType\n\nWith `NewType`, you get a brand new type that can't be used as the type you\nare wrapping.\n\n```scala\ntype Nat = Nat.Type\nobject Nat extends NewType.Default[Int]\n\ndef plus(x: Int, y: Int): Int = x + y\n```\n```scala\nscala\u003e plus(Nat(1), Nat(2))\n\u003cconsole\u003e:19: error: type mismatch;\n found   : Nat.Type\n required: Int\n```\n\nIf you wish for your newtype to be a subtype of the type you are wrapping,\nyou can use `NewSubType` -\n\n```scala\ntype Nat = Nat.Type\nobject Nat extends NewSubType.Default[Int]\n\ndef plus(x: Int, y: Int): Int = x + y\n```\n```scala\nscala\u003e plus(Nat(1), Nat(2))\nres0: Int = 3\n```\n\n## Coercible\n\nThis library introduces the `Coercible` type class for types that can safely\nbe cast to/from newtypes. This is mostly useful when you want to write code\nthat can work generically with newtypes or to simply leverage the compiler\nto tell you when you can do `.asInstanceOf`.\n\n**NOTE: You generally shouldn't be creating instances of Coercible yourself.**\nThis library is designed to create the instances needed for you which are safe.\nIf you manually create instances, you may be permitting unsafe operations which will\nlead to runtime casting errors.\n\nWith that out of the way, here's how we can do safe casting with Coercible -\n\n```scala\ntype Point = Point.Type\nobject Point extends NewType.Of[(Int, Int)]\n```\n```scala\nscala\u003e Coercible[Point, (Int, Int)]\nres10: io.estatico.newtype.Coercible[Point,(Int, Int)] = io.estatico.newtype.Coercible$$anon$1@56c24c2a\n\nscala\u003e Coercible[(Int, Int), Point]\nres11: io.estatico.newtype.Coercible[(Int, Int),Point] = io.estatico.newtype.Coercible$$anon$1@56c24c2a\n\nscala\u003e Coercible[String, Point]\n\u003cconsole\u003e:21: error: could not find implicit value for parameter ev: io.estatico.newtype.Coercible[String,Point]\n       Coercible[String, Point]\n```\n\nThis library provides extension methods for safe casting as well -\n\n```scala\nscala\u003e import io.estatico.newtype.ops._\nimport io.estatico.newtype.ops._\n\nscala\u003e val p = Point(1, 2)\np: Point.Type = (1,2)\n\nscala\u003e p.coerce[(Int, Int)]\nres14: (Int, Int) = (1,2)\n\nscala\u003e (3, 4).coerce[Point]\nres15: Point = (3,4)\n\nscala\u003e (3.2, 4.3).coerce[Point]\n\u003cconsole\u003e:24: error: could not find implicit value for parameter ev: io.estatico.newtype.Coercible[(Double, Double),Point]\n       (3.2, 4.3).coerce[Point]\n                        ^\n```\n\n## Motivation\n\nThe Haskell language provides a `newtype` keyword for creating new types from existing\nones without runtime overhead.\n\n```haskell\nnewtype WidgetId = WidgetId Int\n\nlookupWidget :: WidgetId -\u003e Maybe Widget\nlookupWidget (WidgetId wId) = lookup wId widgetDB\n```\n\nIn the example above, the `WidgetId` type is simply an `Int` at runtime; however, the\ncompiler will treat it as its own type at compile time, helping you to avoid errors.\nIn this case, we can be sure that the ID we are providing to our `lookupWidget` function\nrefers to a `WidgetId` and not some other entity nor an arbitrary `Int` value.\n\nThis library attempts to bring newtypes to Scala.\n\n### Tagged Types\n\nBoth Scalaz and Shapeless provide a feature known as _Tagged Types_. This library\noperates on roughly the same principle except provides the proper infrastructure\nneeded to -\n\n* Control whether newtypes are or are not subtypes of their wrapped type instead of picking\n    a side (Shapeless' are subtypes, Scalaz's are not)\n* Easily provide methods for newtypes\n* Resolve implicits and type class instances defined in the companion object\n* Optimize constructing newtypes via casting with automatic smart constructors\n* Provide facilities to operate generically on newtypes\n* Support safe casting generically via the `Coercible` type class\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Festatico%2Fscala-newtype","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Festatico%2Fscala-newtype","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Festatico%2Fscala-newtype/lists"}