My experience with Sealed Types and Data-Oriented Programming
David Alayachew
davidalayachew at gmail.com
Sun Sep 11 03:22:56 UTC 2022
Hello Rémi,
> The solution is known as type class (see [1] for type class in Scala 3)
sadly Java does not support them (yet ?).
> [1] https://docs.scala-lang.org/scala3/book/ca-type-classes.html
Thank you for introducing me to Scala 3 Type classes. The concept sounds
very similar to interfaces with unimplemented methods, but I assume that
they don't just restrict this to instance methods like Java does?
> Another solution is to switch on classes with
>
> Class<? extends Parseable> parseableType = (...)
Parseable.class.getPermittedSubclasses();
> switch(parseableType) {
> case Identifier.class -> ...
> case Type.class -> ...
> // etc
> }
>
> Here because Parseable is sealed, the compiler knows all the classes so
can do an exhaustive checks, sadly Java does not support them too.
I actually thought of this too myself. I was very disappointed to discover
that it's not possible.
> So if we can not use the compiler, the best is to write unit tests that
will check that there is a field named "regex" of type pattern on all
subclasses and also checks that the is a unit test for each subclasses that
check the string format (by doing reflection) on the unit tests. The idea
is to replace the checks you would like the compiler does for you by unit
tests that ensure that everything follow the "meta-protocol" you have
defined.
>
> By doing the reflection in the tests instead of in the main code avoid to
make the main code slow and you can also check that the test coverage is
good.
I definitely agree that it will improve performance. For this project, I
think I will leave the reflection in the code for now, but I agree that any
future projects that force me to use reflection to enforce a standard
should have all reflection limited to unit tests.
That said, I am still bothered by the fact that I am forced to depend on
reflection. Maybe I am wrong for feeling this way, but reflection feels
like using a second-class solution to solve a first-class problem. And I
say second-class because it feels like I am throwing out type-safety at so
many points, forcing me to operate on assumptions instead. For example,
when calling the Class::getConstructor, I have to pass in a parameter of
List.class to represent my List<String> constructors. Java doesn't allow me
to pass in List<String>.class (understandably). But if I later refactor my
code to take in a List<SomeFoo> instead, this method will still compile
just fine, and won't fail until I try to use it during runtime. That would
put me right back into the same pit that I used reflection to get myself
out of. In order for me to get totality/exhaustiveness, I must give up
type-safety. It feels like a bad trade. I actually ran into this exact
problem during one of my refactors. I managed to get around this by using
even more reflection combined with some hard coded dummy values, but that
feels even worse.
I wish the language gave me some way to test my parameter against all of
the permitted subclasses, see which one succeeds, then return the match
(ideally as a newly created instance), all without giving up the
type-safety and totality that I've had up until that point. Apologies for
repeating myself, I am only using this to demonstrate what I feel a first
class solution would look like.
Thank you so much for your response and insight!
David Alayachew
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/amber-dev/attachments/20220910/fb109653/attachment-0001.htm>
More information about the amber-dev
mailing list