In this post, we’re going through two concrete limitations and pitfalls of neotype.

What are Newtypes?

Newtypes create distinct types from existing ones without runtime overhead.

neotype is a friendly newtype library for Scala 3.

It is built upon Scala 3 opaque types and adds interesting features. See the 5 min tour for more info.

A pitfall with Subtype of Subtype

Context: Let’s consider an entity with a polymorphic association

case class Entity(fk: Id) // polymorphic association

sealed trait Id
object Id {
  case class FooId(value: String) extends Id
  case class BarId(value: String) extends Id
}

And we’d like to use neotype to represent these types (because we need validation, circe codec derivation, …).

type Id = Id.Type
object Id extends Subtype[String]

type FooId = FooId.Type
object FooId extends Subtype[Id]

type BarId = BarId.Type
object BarId extends Subtype[Id]

The problem is that Id is no longer abstract, it’s possible to instantiate it: Id("id")

A limitation with dynamic validation

neotype is great to add validation to our new types.

type Static = Static.Type
object Static extends Subtype[String] {
  override inline def validate(input: String) = 
    input.matches("foo-.*")
}

Static("foo-whatever")

Here the validation is performed at compile time, which is awesome!

But now, if we need to use a prefix defined elsewhere:

val prefix : String = "foo"
type Dynamic = Dynamic.Type
object Dynamic extends Subtype[String] {
  override inline def validate(input: String) = 
    input.matches(s"${prefix}-.*")
}

Dynamic("foo-whatever")

It doesn’t compile anymore, and the message is pretty clear

—— Neotype Error ——————————————————————————————————————————————————————————
I've FAILED to parse Dynamic's validate method!

override inline def validate(input: String) =
    input.matches(s"${prefix}-.*")

Unknown identifier [4mprefix

Neotype works by parsing the AST of your validate method into an executable
expression at compile-time. Sadly, this means I cannot parse arbitrary,
user-defined methods. Likewise, support for the entire Scala standard
library is incomplete.

If you think this expression should be supported, please open an issue at:
[4mhttps://github.com/kitlangton/neotype/issues

The error is due to the way neotype works behind the scenes: it parses the AST and doesn’t support all Scala syntax.

Conclusion

neotype is a great library. Its power comes with some pitfalls and limitations that are minor compared to what it brings to the table.