Exploring Neotype's capabilities, pitfalls, and limitations in Scala 3
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.