{"id":48315482,"url":"https://github.com/rudogma/scala-supertagged","last_synced_at":"2026-04-05T00:30:31.194Z","repository":{"id":57740118,"uuid":"93046708","full_name":"rudogma/scala-supertagged","owner":"rudogma","description":"Unboxed (multi-nested-)tagged + unboxed newtypes. Better and much friendlier alternative to AnyVals.","archived":false,"fork":false,"pushed_at":"2022-11-23T18:55:47.000Z","size":84,"stargazers_count":96,"open_issues_count":5,"forks_count":6,"subscribers_count":3,"default_branch":"master","last_synced_at":"2023-08-15T09:11:31.488Z","etag":null,"topics":["newtype","scala","scalajs","tagged","tagged-types","tagging","type-tagging","types"],"latest_commit_sha":null,"homepage":"","language":"Scala","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/rudogma.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-06-01T10:41:26.000Z","updated_at":"2023-07-07T10:59:08.000Z","dependencies_parsed_at":"2022-08-30T10:50:32.674Z","dependency_job_id":null,"html_url":"https://github.com/rudogma/scala-supertagged","commit_stats":null,"previous_names":[],"tags_count":5,"template":null,"template_full_name":null,"purl":"pkg:github/rudogma/scala-supertagged","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rudogma%2Fscala-supertagged","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rudogma%2Fscala-supertagged/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rudogma%2Fscala-supertagged/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rudogma%2Fscala-supertagged/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rudogma","download_url":"https://codeload.github.com/rudogma/scala-supertagged/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rudogma%2Fscala-supertagged/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31420032,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-05T00:25:07.052Z","status":"ssl_error","status_checked_at":"2026-04-05T00:25:05.923Z","response_time":60,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"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","scalajs","tagged","tagged-types","tagging","type-tagging","types"],"created_at":"2026-04-05T00:30:29.845Z","updated_at":"2026-04-05T00:30:31.181Z","avatar_url":"https://github.com/rudogma.png","language":"Scala","funding_links":[],"categories":[],"sub_categories":[],"readme":"\n[![Build status](https://img.shields.io/travis/rudogma/scala-supertagged/master.svg)](https://travis-ci.org/rudogma/scala-supertagged)\n[![Maven Central](https://img.shields.io/maven-central/v/org.rudogma/supertagged_2.12.svg)](https://maven-badges.herokuapp.com/maven-central/org.rudogma/supertagged_2.12)\n\nTagged \u0026 Newtypes - the better and much friendlier alternative to AnyVals.\n\n# supertagged for scala\n\n- **(multi-nested-)-tagging (compile time subtyping for any class including primitives)**\n    - Unboxed (including primitives)\n    - Nested level tagging\n    - Implicits work without additional imports\n    - Basic blocks for building custom screnarious (ex: runtime refined)\n    - Unapply ready\n    - Multi tagging (like `with` for traits, but for tagged types)\n    - Overtagged (like `extends` for classes, but for tagged types, heavily used in **Scala Superquants** [https://github.com/rudogma/scala-superquants](https://github.com/rudogma/scala-superquants) )\n    \n- **Newtypes**\n    - Unboxed for all, except primitives (primitives boxed to object wrapper java.lang.Integer \u0026 etc) \n    - Simplified \u0026 enriched\n    - Extensible syntax\n    - Nested level newtyping\n    - Implicits work without additional imports\n    - Basic blocks for building custom screnarious (ex: runtime refined)\n    \n\n- **Zero-dependency**\n- **Micro size**\n- **Intellij Idea compatible 100% (`red marks` free)**\n\n\n# SBT\n\nScala: \n- 2.11.x (built with 2.11.12)\n- 2.12.x (built with 2.12.12)\n- 2.13.x (built with 2.13.3)\n```scala\nlibraryDependencies += \"org.rudogma\" %% \"supertagged\" % \"2.0-RC2\"\n```\n\nScalaJS \n- 0.6.х (built with 0.6.33)\n- 1.х (built with 1.1.1)\n```scala\nlibraryDependencies += \"org.rudogma\" %%% \"supertagged\" % \"2.0-RC2\"\n```\n\n# Contents\n\n1. [Bytecode](#bytecode)\n1. [Tagged Types](#tagged-types)\n    1. [Basics](#basics)\n    1. [Postfix syntax](#postfix-syntax-implemented-only-for-tagged-types)\n    1. [Overtagged](#overtagged)\n1. [Newtypes](#newtypes)\n    1. [Basics](#newtypes-basics)\n    1. [Newtypes Custom](#newtypes-custom)\n    1. [More Examples](#more-examples)\n1. [Refined](#refined)\n1. [Lifting typeclasses for tagged \u0026 newtypes](#lifting-typeclasses-for-tagged--newtypes)\n1. [Unapply](#unapply)\n1. [Migration from 1.4 to 2.x](#migration-from-14-to-2x)\n1. [Classic way](#classic-way)\n1. [Alternatives](#alternatives-partially)\n1. [Tests](shared/src/test/scala/supertaggedtests/)\n    \n\n\n\n# Bytecode\n\nFor those who want to check bytecode, have a look at\n- [ShowMeByteCode.scala](https://github.com/Rudogma/scala-supertagged/blob/master/shared/src/test/scala/supertaggedtests/misc/ShowMeByteCode.scala)\n- [ShowMeByteCode.javap.txt](https://github.com/Rudogma/scala-supertagged/blob/master/shared/src/test/scala/supertaggedtests/misc/ShowMeByteCode.javap.txt)\n\n\n\n\n# Tagged Types\n\n## Basics\n```scala\nobject Step extends TaggedType[Raw]{\n    //...implicit scope for Step ...\n    //...put here all implicits you need and they will be found here without additional imports...\n    //...if you want to add more operations to Step, just define one more implicit class with ops...\n    \n    implicit final class Ops(private val value:Type) extends AnyVal {\n        //... your methods here ...    \n    }\n}\ntype Step = Step.Type\n```\n- Now we have built a new subtype `Step \u003c: Raw`. This subtype exists only at compile time.\nSubtyping allow you to call directly all methods of Raw, and put tagged value wherever basic Raw needed without additional conversions\n- In runtime it will be the unboxed Raw type\n- Subtyping don't allow you overriding methods of Raw type.\n- They are very useful, when you don't need strictly newtype, but just need to separate semantics (Ex: if you don't want occasionally mix up `Width` \u0026\u0026 `Height`)\n- You can define any additional methods to your new subtypes via implicit class\n\n\n\n```scala\nobject WidthT extends TaggedTypeT {\n    type Raw[T] = T\n}\ntype WidthT[T] = WidthT.Type[T]\n\nval v1:WidthT[Int] = WidthT[Int](5) // WidthT.apply[Int](5)\nval v2:WidthT[Long] = WidthT[Long](5L) \n```\n- You can define parameterized tagged type. For simple cases with 1 type parameter you can use `TaggedTypeT`\n- Here we have at runtime: `v1:Int` \u0026\u0026 `v2:Long`\n\n\n\n```scala\nobject Counters extends TaggedType[T]{\n  type Raw[T] = Array[T]\n}\ntype Counters[T] = Counters.Type[T]\n\nval v1:Counters[Long] = Counters[Long](Array(5L))\nval v2:Counters[String] = Counters[String](Array(\"String\"))\n```\n- Here we will tag an Array[T]\n- In runtime `v1:Array[long]`(array of primitives) \u0026\u0026 `v2:Array[String]`\n\n\n### Nested tagging\n\n```scala\nval arr:Array[Array[Array[Array[Int]]]]\nval v1 = Width(arr) // searches for first Int and replaces it -\u003e Array[Array[Array[Array[Width]]]]\nval v2 = WidthT[Int](arr) // searches for first Int and replaces it -\u003e Array[Array[Array[Array[WidthT[Int]]]]]\nval v3 = Counters[Int](arr) // searches for first Array[Int] and replaces it -\u003e Array[Array[Array[Counters]]]\n```\n- You can tag at arbitrary nested level (the most outer suitable level will be used)\n\n\n## Postfix syntax (implemented only for tagged types)\n\n```scala\nimport supertagged.postfix._\n\nvalue @@ Width\nvalue @@@ Width\nvalue !@@ Width\nvalue untag Width\n```\n- Requires manual import `import supertagged.postfix._`\n\n## Overtagged\n\n- Ex: [OverTaggedTest](https://github.com/rudogma/scala-supertagged/blob/master/shared/src/test/scala/supertaggedtests/tagged/OverTaggedTest.scala)\n\n\n# Newtypes\n\n## Newtypes Basics\n```scala\nobject Step extends NewType[Raw]{\n    //...implicit scope for Step ...\n    //...put here all implicits you need and they will be found here without additional imports...\n    //...if you want to add more operations to Step, just define one more implicit class with ops...\n    \n    implicit final class Ops(private val value:Type) extends AnyVal {\n        //... your methods here ...    \n    }\n    \n    implicit def someCommonImplicit = ... // conversions, wrappers, typeclasess \u0026 etc...\n}\ntype Step = Step.Type\n```\n- Now we have built a newtype Step. This newtype exists only at compile time and till the last phases with erasure it totally different for compiler\n- As a result of newtyping, you can't call directly methods of Raw and using newtyped value wherever basic Raw needed requires additional conversions (at compile time and some auto-generated boiler plate bytecode, but will not affect on performance)\n- At runtime it will be the unboxed Raw type (It is true for all, except primitives. Primitive types will be boxed in their appropriate object wrapper. Ex: int -\u003e java.lang.Integer, etc...) \n- To call raw methods, you need to make de-newtyping. You have built in method in wrapper for this. Ex: `Step.raw(newtyped)`  (You can still define your own method for newtype via implicit class to call it like this `newtypedValue.raw` - or any other name as you wish)\n- Newtyping allows you \"overriding\" all methods(except toString) of Raw type. Compiler don't see any methods of Raw type when working with it, so he will try to search for `implicit class` to resolve methods and generate appropriate code for it (ex: `new YouImplicitClass(newtypedValue).yourMethodName()` (or more efficient if you use `extends AnyVal` - according to the documentation of Scala)). \n- You still can define any additional methods to you new newtypes\n\n\n```scala\nobject WidthT extends NewTypeT {\n    type Raw[T] = T\n}\ntype WidthT[T] = WidthT.Type[T]\n\nval v1:WidthT[Int] = WidthT[Int](5)\nval v2:WidthT[Long] = WidthT[Long](5L) \n```\n- You can define parameterized newtypes. For simple cases with 1 type parameter you can use `NewTypeT`\n- Here we have at runtime: `v1:java.lang.Integer` \u0026\u0026 `v2:java.lang.Long`\n\n\n\n```scala\nobject Counters extends TaggedType[T]{\n  type Raw[T] = List[T]\n}\ntype Counters[T] = Counters.Type[T]\n\nval v1:Counters[Long] = Counters[Long](List(5L)) // You can't make newtype from Array[primitives], because it will fail at runtime with `can't cast` error\nval v2:Counters[String] = Counters[String](List(\"String\"))\n```\n- Here we will tag an List[T]\n- In runtime `v1:List[java.lang.Long]`(array of primitives) \u0026\u0026 `v2:List[String]`\n- You can make newtypes from `Array[T \u003c: AnyRef]`\n\n\n\n```scala\nval arr:List[List[List[List[Int]]]]\nval v1 = Width(arr) // -\u003e Array[Array[Array[Array[Width]]]]\nval v2 = Width[Int](arr) // -\u003e Array[Array[Array[Array[Width[Int]]]]]\nval v3 = Counters[Int](arr) // -\u003e Array[Array[Array[Counters]]]\n```\n- You can make newtypes at arbitrary nested level\n\n\n## Newtypes Custom\n\n```scala\nobject Unfold extends NewType0 {\n\n    protected type T[A,B] = A =\u003e Option[(A, B)]\n    type Type[A,B] = Newtype[T[A,B],Ops[A,B]]\n    \n    \n    implicit final class Ops[A,B](private val f: Type[A,B]) extends AnyVal {\n      def apply(x: A): Stream[B] = raw(f)(x) match {\n        case Some((y, e)) =\u003e e #:: apply(y)\n        case None =\u003e Stream.empty\n      }\n    }\n    \n    def apply[A,B](f: T[A,B]):Type[A,B] = tag(f) // `tag` built in helper\n    def raw[A,B](f:Type[A,B]):T[A,B] = cotag(f) // `cotag` built in helper\n}\ntype Unfold[A,B] = Unfold.Type[A,B]\n\ndef digits(base:Int) = Unfold[Int,Int]{\n    case 0 =\u003e None\n    case x =\u003e Some((x / base, x % base))\n}\n\ndigits(10)(123456).force.toString shouldEqual \"Stream(6, 5, 4, 3, 2, 1)\"\n```\n- You can use `NewType0` as base for your custom semantics and complex types\n\n## More examples\nAt: [newtypes tests](https://github.com/rudogma/scala-supertagged/blob/master/shared/src/test/scala/supertaggedtests/newtypes/)\n\n\n\n# Refined\n\n```scala\nobject Meters extends TaggedType0[Long] {\n    def apply(value:Long):Type = if(value \u003e= 0) TaggedOps(this)(value) else throw new Exception(\"Can't be less then ZERO\")\n    \n    def option(value:Long):Option[Type] = if(value \u003e= 0) Some( TaggedOps(this)(value)) else None\n}\ntype Meters = Meters.Type\n\n\nMeters(-1) // will throw Exception\nMeters(5) // would be `5:Meters`\nMeters.option(-1) // would be `None`\nMeters.option(0) // would be `Some(0:Meters)`\n\n```\n\n- You can use `TaggedType0` \u0026 `NewType0` as base for defining your subtypes \u0026 newtypes with `refined` semantics (in any way you want)\n- Ex: Meters\n    - [Define](https://github.com/rudogma/scala-supertagged/blob/master/shared/src/test/scala/supertaggedtests/tagged/package.scala#L108)\n    - [Using](https://github.com/rudogma/scala-supertagged/blob/master/shared/src/test/scala/supertaggedtests/tagged/Refined.scala)\n\n# Lifting typeclasses for tagged \u0026 newtypes\n\nYou have several options:\n\n\n### 1. Using LiftF for concrete F \u0026\u0026 all tagged types\n\n```scala\n\nimport supertagged.@@\nimport supertagged.lift.LiftF\nimport io.circe.Encoder\n\nimplicit def lift_circeEncoder[T,U](implicit F:Encoder[T]):Encoder[T @@ U] = LiftF[Encoder].lift\nimplicit def lift_circeDecoder[T,U](implicit F:Decoder[T]):Decoder[T @@ U] = LiftF[Decoder].lift\n```\n\n### 2. Using LiftF for concrete F \u0026\u0026 concrete Tag\n\n```scala\nimport supertagged.lift.LiftF\n\nimplicit val step_circeEncoder = LiftF[io.circe.Encoder].lift[Step.Raw, Step.Tag]\n// -or-\nimplicit val step_circeEncoder:io.circe.Encoder[Step] = LiftF[io.circe.Encoder].lift\n```\n\n### 3. Using helper trait and mixing it when you need (works for TaggedType \u0026 NewType, can be adopted for more complex types)\n\n```scala\n\ntrait LiftedCirce {\n    type Raw\n    type Type\n    \n    implicit def ordering(implicit origin:Ordering[Raw]):Ordering[Type] = unsafeCast(origin)\n}\n\ntrait LiftedCirceT {\n    type Raw[T]\n    type Type[T]\n    \n    implicit def circeEncoder[T](implicit origin:io.circe.Encoder[Raw[T]]):io.circe.Encoder[Type[T]] = unsafeCast(origin)\n    implicit def circeDecoder[T](implicit origin:io.circe.Decoder[Raw[T]]):io.circe.Decoder[Type[T]] = unsafeCast(origin)\n}\n\nobject Step extends TaggedType[Int] with LiftedCirce\ntype Step = Step.Type\n\n```\n\n\n### 4. Using method @lift from TaggedType, NewType traits\n\n```scala\nobject Step extends TaggedType[Int] {\n  // (they will be used without additional imports)\n  implicit def circeEncoder:io.circe.Encoder[Type] = lift\n  implicit def circeDecoder:io.circe.Decoder[Type] = lift\n}\ntype Step = Step.Type\n\n```\nOr in place:\n```scala\nobject Step extends TaggedType[Int]\ntype Step = Step.Type\n\n\n//somewhere else...\n{\n  import Step\n  \n  val liftedEncoder:io.circe.Encoder[Step] = Step.lift\n  val liftedEncoder = Step.lift[io.circe.Encoder] // will be  io.circe.Encoder[Step]\n}\n```\n\n\n### 5. Using liftAnyF [not recommended]\n\nWill try auto lift of any requested typeclass\nNot recommended. Because of loosing control of what you are lifting.\n```scala\nimport supertagged.lift.liftAnyF\n\ncallMethodWithImplicitTypeClass(step)\n```\n\n\n\n# Unapply\n\n```scala\nval width = Width(5)\n\nwidth match {\n  case Width(5) =\u003e //...\n}\n```\n- This will work out of the box for types based on `TaggedType[Raw]` \u0026\u0026 `NewType[Raw]`\n- Can be easily adopted for more complex types\n\n\n```scala\nval widthInt = WidthT[Int](5)\nval EInt = WidthT.extractor[Int] // boiler plate, because Scala `match` don't support syntax with type parameters at now. Ex: `case EInt.extractor[Int](1)`\n\nwidthInt match {\n  case EInt(1) =\u003e false\n  case EInt(5) =\u003e true\n}\n```\n- This will work for `TaggedTypeT` \u0026\u0026 `NewTypeT` with some boilerplate\n\n\n# Migration from 1.4 to 2.x\n\n- Classic\n    - moved to `supertagged.classic`\n- Postfix syntax for tagged types\n    - moved to `supertagged.postfix` \n    - use `import supertagged.postfix._` if you really need it\n- Multitagging \n    - Before: `@@` - double @ used to multi tag\n    - Now: `@@@` - use explicit triple @ to add multi tag\n- TaggedTypeF \u0026 NewTypeF\n    - Replaced with new scheme\n    - Checkout docs above and tests for more examples\n    - Now: \n        - trait TaggedType (trait NewType )   -- for concrete types\n        - trait TaggedTypeT (trait NewTypeT ) -- for parametric tagged type\n        - trait TaggedType0 (trait NewType0 ) -- base block for building custom tagged types with any semantics\n- `implicit def ordering` removed from TaggedType\n    - Use explicit mixin `with LiftedOrdering` \u0026\u0026 `with LiftedOrderingT` if you want to use lifted orderings for your model \n    - Example: [OrderingTest](supertaggedtests.tagged.OrderingTest.scala)\n    - More: [Lifting typeclasses](#lifting-typeclasses-for-tagged--newtypes)\n- Lifting ops\n    - moved to package `supertagged.lift` (now occasionally `import supertagged._` will bring only one implicit `@newtypeOps` to scope)\n    - implicit method @liftLifterF\n        - Deprecated and removed, because of many collisions\n        - Now: Look at section `Lifting typeclasses`\n- Scalac bug `polymorphic expression cannot be instantiated` gone away (because of: can't reproduce after changing internals in supertagged)\n\n\n\n# Specific Scalac BUG\n\n### Polymorphic expression cannot be instantiated\n\nIn versions before `2.0` was scalac specific bug in some very specific cases. Now it is absent.\n\n### Overriding `+` for Newtypes\n\n1. Everybody knows about existing autoimport `Predef.scala`. But it is not obvious that there is a weird `implicit final class any2stringadd { def +(other: String)... }`. Because of autoimport it used by compiler as direct scope for searching implicits. It means, that companion objects are not checked if he has found appropriate implicit in direct scope. So, he will use this `any2stringadd.+` if you write `x:MyNewType) + argument`\n2. In further versions of Scala this class will be removed, but now you have few variants to overcome these:\n    1. `import Predef.{any2stringadd =\u003e _,_}` - Shadowing\n    2. `import supertagged.newtypeOps` - will force compiler to check companion object for newtype and prefer implicit ops from it.\n\n\n\n\n# Classic way\n\nOriginal idea by [Miles Sabin](https://gist.github.com/milessabin/89c9b47a91017973a35f).\nSimilar implementations are also available in [shapeless](https://github.com/milessabin/shapeless) \nand [scalaz](https://github.com/scalaz/scalaz).\n```scala\nimport supertagged.classic.@@\n\nsealed trait Width\nval value = @@[Width](5) // value is `Int @@ Width`\n```\n\n\n# Alternatives (partially)\n- Estatico's [scala-newtype](https://github.com/estatico/scala-newtype) (based on macros, not bad alternative)\n- **Alexander Semenov**'s [Tagged-Types](https://github.com/Treev-io/tagged-types/)\n- [shapeless tags \u0026 newtype (classic)](https://github.com/milessabin/shapeless) (very poor)\n- [scalaz tags](https://github.com/scalaz/scalaz/blob/0e75c20aee7634de06fe94094cceac7a3e5fe305/core/src/main/scala/scalaz/Tag.scala) (only tagged, very poor)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frudogma%2Fscala-supertagged","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frudogma%2Fscala-supertagged","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frudogma%2Fscala-supertagged/lists"}