{"id":20772477,"url":"https://github.com/rcardin/value-classes","last_synced_at":"2026-05-25T04:31:34.835Z","repository":{"id":82060791,"uuid":"336378545","full_name":"rcardin/value-classes","owner":"rcardin","description":null,"archived":false,"fork":false,"pushed_at":"2021-02-18T21:00:53.000Z","size":70,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-01-18T07:27:43.364Z","etag":null,"topics":["article","scala","value-types"],"latest_commit_sha":null,"homepage":"","language":"Scala","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/rcardin.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"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}},"created_at":"2021-02-05T20:14:12.000Z","updated_at":"2021-02-18T21:00:55.000Z","dependencies_parsed_at":null,"dependency_job_id":"f89edadc-9dc6-4a61-bd70-4af708ca54c8","html_url":"https://github.com/rcardin/value-classes","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rcardin%2Fvalue-classes","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rcardin%2Fvalue-classes/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rcardin%2Fvalue-classes/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rcardin%2Fvalue-classes/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rcardin","download_url":"https://codeload.github.com/rcardin/value-classes/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243111488,"owners_count":20238168,"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":["article","scala","value-types"],"created_at":"2024-11-17T12:21:28.326Z","updated_at":"2025-12-26T04:17:33.520Z","avatar_url":"https://github.com/rcardin.png","language":"Scala","funding_links":[],"categories":[],"sub_categories":[],"readme":"One of the main rules of functional developers is that we should always trust a function's signature. Hence, when we use functional programming, we prefer to define _ad-hoc_ types to represent simple information such as an identifier, a description, or a currency. Ladies and gentlemen, please welcome the value classes.\n\n## 1. The Problem\n\nFirst, let's define an example to work with. Imagine we have an e-commerce business and that we model a product to sell using the following representation:\n\n```scala\ncase class Product(code: String, description: String)\n```\n\nSo, every product is represented by a `code` (which can mean some barcode), and a `description`. So far, so good. Now, we want to implement a repository that retrieves products from a persistent store, and we want to allow our users to search by `code` and by `description`:\n\n```scala\ntrait ProductRepository {\n  def findByCode(code: String): Option[Product]\n  def findByDescription(description: String): List[Product]\n}\n```\n\nWe cannot avoid using a description in the search by code or a code in the search by description. As we are representing both pieces of information through a `String`, we can wrongly pass a description to the search by code, and vice versa: \n\n```scala\nval aCode = \"8-000137-001620\"\nval aDescription = \"Multivitamin and minerals\"\n\nProductRepository.findByCode(aDescription)\nProductRepository.findByDescription(aCode)\n```\n\nThe compiler cannot warn us of our errors because we represent both pieces of information, i.e. the `code` and the `description`, using simple `Strings`. This fact can lead to subtle bugs, which are very difficult to intercept at runtime as well.\n\n## 2. Using Straight Case Classes\n\nHowever, we are smart developers, and we want the compiler to help us identify such errors as soon as possible. Fail fast, they said. Hence, we define two dedicated types, both for the `code`, and for the `description`:\n\n```scala\ncase class BarCode(code: String)\ncase class Description(txt: String)\n```\n\nThe new types, `BarCode` and `Description`, are nothing more than wrappers around strings. In jargon, we call them _value classes_. However, they allow us to refine the functions of our repository to avoid the previous information mismatch:\n\n```scala\ntrait AnotherProductRepository {\n  def findByCode(barCode: BarCode): Option[Product] =\n    Some(Product(barCode.code, \"Some description\"))\n  def findByDescription(description: Description): List[Product] =\n    List(Product(\"some-code\", description.txt))\n}\n```\n\nAs we can see, it is not possible anymore to search a product by code while accidentally passing a description. Indeed, we can try to pass a `Description` instead of a `BarCode`:\n\n```scala\nval anotherDescription = Description(\"A fancy description\")\nAnotherProductRepository.findByCode(anotherDescription)\n```\n\nAs desired, the compiler diligently warns us that we are bad developers:\n\n```shell\n[error] /Users/rcardin/Documents/value-types/src/main/scala/ValuesTypes.scala:33:39: type mismatch;\n[error]  found   : in.rcard.value.ValuesTypes.Description\n[error]  required: in.rcard.value.ValuesTypes.BarCode\n[error]   AnotherProductRepository.findByCode(anotherDescription)\n[error]                                       ^\n```\n\nHowever, we can still create a `BarCode` using a `String` representing a description:\n\n```scala\nval aFakeBarCode: BarCode = BarCode(\"I am a bar-code\")\n```\n\nTo overcome this issue we must use the _smart constructor_ design pattern. Though the description of the pattern is beyond the scope of this article, the smart constructor pattern hides to developers the main constructor of the class, and adds a factory method that performs any needed validation. In its final form, smart constructor pattern for the `BarCode` type is the following:\n\n```scala\nsealed abstract class BarCodeWithSmartConstructor(code: String)\nobject BarCodeWithSmartConstructor {\n  def mkBarCode(code: String): Either[String, BarCodeWithSmartConstructor] =\n    Either.cond(\n      code.matches(\"\\\\d-\\\\d{6}-\\\\d{6}\"),\n      new BarCodeWithSmartConstructor(code) {},\n      s\"The given code $code has not the right format\"\n    )\n}\n\nval theBarCode: Either[String, BarCodeWithSmartConstructor] =\n  BarCodeWithSmartConstructor.mkBarCode(\"8-000137-001620\")\n```\n\nAwesome! We reach our primary goal. Now, we have fewer problems to worry about...or not?\n\n## 3. An Idiomatic Approach\n\nThe above approach resolves some problems, but it adds many others. In fact, since we are using a `class` to wrap `String`s, the compiler must instantiate a new `BarCode` and `Description` every single time. The over instantiation of objects can lead to a problem concerning performance and the amount of consumed memory.\n\nFortunately, Scala provides an idiomatic way to implement value classes. Idiomatic value classes avoid allocating runtime objects and the problems we just enumerated.\n\nA idiomatic value class is a `class` (or a `case class`) that extends the type `AnyVal`, and declares only one single public `val` attribute in the constructor. Moreover, a value class can declare `def`:\n\n```scala\ncase class BarCodeValueClass(val code: String) extends AnyVal {\n  def countryCode: Char = code.charAt(0)\n}\n```\n\nHowever, value classes have many constraints: They can define `def`, but not `val` other than the constructor's attribute, cannot be extended, and cannot extend anything but _universal traits_ (for the sake of completeness, a universal trait is a trait that extends the `Any` type, has only `def` as members, and does no initialization).\n\nThe main characteristic of a value class is that the compiler treats it as a `case class` at compile-time. Still, at runtime, its representation is equal to the type declared in the constructor. Roughly speaking, the `BarCodeValueClass` type is transformed as a simple `String` at runtime.\n\nHence, due to the lack of runtime overhead, value classes are a valuable tool used in the SDK to define extension methods for basic types such as `Int`, `Double`, `Char`, etc.\n\n### 3.1. The Problem With the Idiomatic Approach\n\nWe must remember that the JVM doesn't support value classes directly. So, there are cases in which the runtime environment must perform an extra allocation of memory for the wrapper type.\n\nThe Scala [documentation](https://docs.scala-lang.org/overviews/core/value-classes.html) reports the following use cases that need an extra memory allocation:\n\n* A value class is treated as another type.\n* A value class is assigned to an array.\n* Doing runtime type tests, such as pattern matching.\n\nUnfortunately, the first rule's concrete case also concerns using a value class as a type argument. Hence, also the use of a simple generic method `show`, creating a printable representation of an object, can cause an undesired instantiation:\n\n```scala\ndef show[T](obj: T): String = obj.toString\nprintln(show(BarCodeValueClass(\"1-234567-890234\")))\n```\n\nMoreover, for the same reason, every time we want to implement the type classes pattern for a value class, we cannot avoid its instantiation. We love type classes, as functional developers, and many Scala libraries, such as Cats, are based on the root of the type classes pattern. So, this is a big problem.\n\nThe second rule concerns the use of a value class inside an array. For example, imagine we want to create a bar-code basket:\n\n```scala\nval macBookBarCode = BarCodeValueClass(\"1-234567-890234\")\nval iPhone12ProBarCode = BarCodeValueClass(\"0-987654-321098\")\nval barCodes = Array[BarCodeValueClass](macBookBarCode, iPhone12ProBarCode)\n```\n\nAs expected, the `barCodes` array will contain `BarCodeValueClass` instances, and not a `String` primitive. Again, additional instantiations are needed. In detail, the problem is not due to Scala, but to how the JVM treats arrays of objects and arrays of primitive types.\n\nFinally, as the third rule states, we cannot use a value class with pattern matching avoiding a runtime instantiation. Hence, the following method, testing if a bar-code represents a product made in Italy, forces a runtime instantiation of the `barCode` object as a `BarCodeValueClass`:\n\n```scala\ndef madeInItaly(barCode: BarCodeValueClass): Boolean = barCode match {\n  case BarCodeValueClass(code) =\u003e code.charAt(0) == '8'\n}\n```\n\nDue to these limitations, the Scala community searched for a better solution. Ladies and gentlemen, please welcome the [NewType](https://github.com/estatico/scala-newtype) library.\n\n## 4. The NewType Library\n\nThe NewType library allows us to create new types without the overhead of extra runtime allocations, avoiding the pitfalls of Scala values classes. To use it, we need to import the proper dependency in the `build.sbt` file:\n\n```sbt\nlibraryDependencies += \"io.estatico\" %% \"newtype\" % \"0.4.4\"\n```\n\nIt uses the experimental feature of Scala macros. So, it is necessary to enable it at compile-time, using the `-Ymacro-annotations`. In details, the library defines the `@newtype` annotation macro:\n\n```scala\nimport io.estatico.newtype.macros.newtype\n@newtype case class BarCode(code: String)\n```\n\nThe macro expansion generates a new `type` definition and an associated companion object. Hence, we must define the new type inside an `object` or a `package object`. Moreover, the library expands the class marked with the `@newtype` annotation with its underlying value at runtime. So, a `@newtype` class can't extend any other type.\n\nDespite these limitations, the NewType library works like a charm and interacts smoothly with IDEs. \n\nUsing two `@newtype`s, one representing a bar-code and one representing a description, we can easily improve the definition of the initial `Product` class:\n\n```scala\n@newtype case class BarCode(code: String)\n@newtype case class Description(descr: String)\n    \ncase class Product(code: BarCode, description: Description)\n```\n\nMoreover, creating a new instance of a newtype it's as easy as creating an instance of a Scala regular type:\n\n```scala\nval iPhoneBarCode: BarCode = BarCode(\"1-234567-890123\")\nval iPhoneDescription: Description = Description(\"Apple iPhone 12 Pro\") \nval iPhone12Pro: Product = Product(iPhoneBarCode, iPhoneDescription)\n```\n\nAs we can see, the code looks like the original `Product` definition. However, we altogether avoid the runtime instantiation of the wrapper classes. Such an improvement!\n\nWhat about smart constructors? If we choose to use a `case class`, the library will generate the `apply` method in the companion object. If we want to avoid access to the `apply` method, we can use a `class` instead and create our smart constructor in a dedicated companion\nobject:\n\n```scala\n@newtype class BarCodeWithCompanion(code: String)\n\nobject BarCodeWithCompanion {\n  def mkBarCode(code: String): Either[String, BarCodeWithCompanion] =\n    Either.cond(\n      code.matches(\"\\\\d-\\\\d{6}-\\\\d{6}\"),\n      code.coerce,\n      s\"The given code $code has not the right format\")\n}\n```\n\n### 4.1. Type Coercion\n\nWait. What is the `code.coerce` statement? Unfortunately, using a `class` instead of a `case class` removes the chance to use the `apply` method for other developers and us. So, we have to use type coercion.\n\nAs we know, the Scala community considers type coercion a bad practice because it requires a cast (via the `asInstanceOf` method). The NewType library tries to make this operation safer using a type class approach.\n\nHence, the compiler will let us coerce between types if and only if an instance of the `Coercible[R, N]` type class exists in the scope for types `R` and `N`. Fortunately, the NewType library does the dirty work for us, creating the needed `Coercible` type class instances. Taking our example, the generated `Coercible` type classes let us cast from `BarCode` to `String`, and vice versa:\n\n```scala\nval barCodeToString: Coercible[BarCode, String] = Coercible[BarCode, String]\nval stringToBarCode: Coercible[String, BarCode] = Coercible[String, BarCode]\n\nval code: String = barCodeToString(iPhoneBarCode)\nval iPhone12BarCode: BarCode = stringToBarCode(\"1-234567-890123\")\n```\n\nHowever, if we try to coerce a `Double` to a `BarCode`, the compiler will not find the needed type class:\n\n```scala\nval doubleToBarCode: Coercible[Double, BarCode] = Coercible[Double, BarCode]\n```\n\nIn fact, the above code makes the compiler yelling:\n\n```sbt\n[error] could not find implicit value for parameter ev: io.estatico.newtype.Coercible[Double,in.rcard.value.ValuesClasses.NewType.BarCode]\n[error]       val doubleToBarCode: Coercible[Double, BarCode] = Coercible[Double, BarCode]\n[error]                                                                  ^\n```\n\nAs the type classes pattern recommends, the NewType library defines also an extension method, `coerce`, for the types with a `Coercible` type class associated:\n\n```scala\nval anotherCode: String = iPhoneBarCode.coerce\nval anotherIPhone12BarCode: BarCode = \"1-234567-890123\".coerce\n```\n\nHowever, [it's proven](https://github.com/estatico/scala-newtype/issues/64) that the scope resolution of the `Coercible` type class (a.k.a., the coercible trick) is an operation with a very high compile-time cost and should be avoided. Moreover, as the [library documentation](https://github.com/estatico/scala-newtype#coercible) says\n\n\u003e You generally shouldn't be creating instances of Coercible yourself. This library is designed to create the instances needed for you which are safe. If you manually create instances, you may be permitting unsafe operations which will lead to runtime casting errors.\n\n### 4.2. Automatically Deriving Type Classes\n\nThe NewType library offers a very nice mechanism for deriving type classes for our `newtype`. Taking an idea coming from Haskell (as the library itself), the generated companion object of a `newtype` contains two methods, called `deriving` and `derivingK`.\n\nWe can call the first method `deriving`, if we want to derive an instance of a type class with the type parameter that is not higher kinded. For example, we want to use our `BarCodeWithCompanion` type together with the `cats.Eq` type class:\n\n```scala\nimplicit val eq: Eq[BarCodeWithCompanion] = deriving\n```\n\nWhereas, if we want to derive an instance of a type class with the type parameter that is higher kinded, we can use the `derivingK` method instead.\n\nTherefore, we can quickly implement type classes for newtypes should dispel any doubt whether using them to value classes.\n\nHowever, with Dotty's advent (a.k.a. Scala 3), a new competitor came in town: The opaque types.\n\n## 5. Scala 3 Opaque Types Aliases\n\nAs many of us might already know, Dotty is the former name of the new major version of Scala. Dotty introduces many changes and enhancements to the language. One of these is _opaque type aliases_, which addresses the same issue as the previous value classes: Creating zero-cost abstraction.\n\nIn effect, opaque types let us define a new `type` alias with an associated scope. Hence, Dotty introduces a new reserved word for opaque type aliases, `opaque`:\n\n```scala\nobject BarCodes {\n  opaque type BarCode = String\n}\n```\n\nTo create a `BarCode` from a `String`, we must provide one or many smart constructors:\n\n```scala\nobject BarCodes {\n  opaque type BarCode = String\n  \n  object BarCode {\n    def mkBarCode(code: String): Either[String, BarCode] = {\n      Either.cond(\n        code.matches(\"\\\\d-\\\\d{6}-\\\\d{6}\"),\n        code,\n        s\"The given code $code has not the right format\"\n      )   \n    }\n  }\n}\n```\n\nInside the `BarCodes` scope, the `type` alias `BarCode` works as a `String`: We can assign a `String` to a variable of type `BarCode`, and we have access to the full API of `String` through an object of type `BarCode`. So, there is no distinction between the two types:\n\n```scala\nobject BarCodes {\n  opaque type BarCode = String\n  val barCode: BarCode = \"8-000137-001620\"\n  \n  extension (b: BarCode) {\n    def country: Char = b.head\n  }\n}\n```\n\nAs we can see, if we want to add a method to an opaque type alias, we can use the extension method mechanism, which is another new feature of Dotty.\n\nOutside the `BarCodes` scope, the compiler treats a `String` and a `BarCode` as completely different types. In other words, the `BarCode` type is opaque with respect to the `String` type outside the definition scope:\n\n```scala\nobject BarCodes {\n  opaque type BarCode = String\n}\nval anotherBarCode: BarCode = \"8-000137-001620\"\n```\n\nHence, in the above example, the compiler diligently warns us that the two types are incompatible:\n\n```shell\n[error] 20 |  val anotherBarCode: BarCode = \"8-000137-001620\"\n[error]    |                      ^^^^^^^\n[error]    |                      Not found: type BarCode\n```\n\nFinally, we can say that the opaque type aliases seem to be the idiomatic replacement to the NewType\nlibrary in Dotty / Scala 3. Awesome!\n\n## 6. Conclusion\n\nSumming up, in this article, we have first introduced the reason why we need the so-called value classes. The first attempt to give a solution uses `case class`es directly. However, due to performance concerns, we introduced the idiomatic solution provided by Scala. This approach, too, had limitations due to random memory allocations.\n\nThen, we turned to additional libraries, and we found the NewType library. Through the use of a mix of `type` and companion objects definition, the library solved the value classes problem in a very brilliant way.\n\nFinally, we looked at the future, introducing opaque type aliases from Dotty that give us the idiomatic language solution we were searching for.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frcardin%2Fvalue-classes","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frcardin%2Fvalue-classes","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frcardin%2Fvalue-classes/lists"}