"Tagged" Transparent New-Types are a distinct use-case that would benefit from mention in the documentation imho #288
Replies: 3 comments 2 replies
-
Hello!
How about opaque type Temperature <: Double = Double :| Positive
object Temperature extends RefinedTypeOps[Double, Positive, Temperature]
opaque type Moisture <: Double = Double :| Positive
object Moisture extends RefinedTypeOps[Double, Positive, Moisture] val temperature: Temperature = Moisture(10) //Does not compile
val n: Double = Moisture(10) //Compiles |
Beta Was this translation helpful? Give feedback.
-
Right, yes, that works and doesn't need to add a Also, the subtype bound can include a refinement, eg
To me, the tagging style appears more uniform however, in that it uses the Iron Constraint mechanism for all type distinctions. Whereas, the opaque modifier here acts to make the IronType "disagreeable", ie won't be equal to any other type that has the same bound. It's like part of the overall refinement is implemented by a different mechanism, opacity. In this example, we see some confusing behavior if ever we try to mix opaque and transparent refinements.
Also, I noticed the section on Typeclass Derivation where it says "Usually, transparent type aliases do not need a special handling for typeclass derivation as they can use the given instances for IronType. However, this is not the case for opaque type aliases". Actually, I realise I'm not 100% clear on when typeclass derivation doesn't work on opaque types... would transparent+tag work better in this aspect? |
Beta Was this translation helpful? Give feedback.
-
Actually, I have found another, imo compelling, apparent reason why Opaque Aliases with a Bound (eg The problem with transparent aliases is that they don't seem to have a companion object. After all, they are not a distinct type, just a reference to another type. Without a companion object, it is not possible to user-define extension methods for the refined type. The way I use tagged refined types, this is a major limitation.
A mystery is, is that line |
Beta Was this translation helpful? Give feedback.
-
Please interpret my suggestions in the context of the New Types section of the docs. (I ❤️ Iron BTW... totally agree why refined types are so important and useful. And Iron is honestly, significantly more usable than Refined used to be).
The problem: The docs speak as though there are two distinct use-case scenarios for new types, which correspond roughly to transparent vs opaque type aliases. They present an example, copied below ,to motivate the second, opaque type-based solution, describing it as to "avoid mixing different domain types with the same refinement".
The problem is that it is possible to achieve this separation using transparent aliases only, no opaque types (Scastie). The docs currently advocate an opaque-type solution that also prevents upcasts to the base type, which brings additional complexity and is often not desired.
The key is to "tag" each refinement with a unique string that establishes a separate subtype, yet still within the base type.
The way I think about it is illustrated below. There are three distinct cases:
A problem I perceive atm in Scala 3 codebases generally is a lot of (IMO over-)use of opaque types when people just want the capabilities of transparent aliases, with tags. Please consider mentioning this option in the documentation 🙏 😄
Beta Was this translation helpful? Give feedback.
All reactions