Completely agree with this. There’s no reason to rule out sugar in the future, but I strongly oppose letting one particular use case dictate what the general case should look like at the expense of other use cases that don’t fit.
A completely feasible sugar could be something like:
case opaque type Name(n: String) >: Lower <: Upper
Let’s say that’s sugar for:
opaque type Name = String >: Lower <: Upper
object Name {
def apply(n: String): Name = n
def unapply(n: Name): Some[String] = Some(n)
}
(Also let’s assume that the Some
is optimized away somehow, because otherwise what’s the point of all this?)
The problem here is that I would expect this to have the same semantics as newtype
in Haskell. But Scala is not Haskell. What makes newtype
work well in Haskell is that it behaves almost exactly like data
. The only difference has to do with laziness, and that’s really not relevant here.
The biggest difference by far One of the many differences between Haskell and Scala is how pattern matching works in general. In Scala I can pattern match on anything:
(any: Any) match {
case Name(str) => Some(str)
case _ => None
}
This is not legal in Haskell:
newtype Name = Name String
anyToName :: forall a. a -> Maybe String
anyToName x = case x of
Name str -> Just str
_ -> Nothing
The price we pay for this is that Scala will gladly let us do this:
scala> def badMatch(s: String): Name = s match {case n @ Name(_) => n}
def badMatch(s: String): o.Name
scala> badMatch("hey")
val res0: o.Name = hey
I was surprised the compiler let me do this, because if Name would’ve been a case class instead, this would not even compile. But even if that case was made illegal, the first snippet would match on any String.
What I’m trying to say here is that while I think most people could understand why typed patterns should be avoided, it’s harder to understand why extractor patterns are unsafe.
There are solutions to this. For example, some problems would go away if extractor patterns were restricted so that the type of the scrutinee must always conform to the argument type in the unapply
method. It’s not trivial to see what such a change would break.
The actual semantics of opaque types integrate very well into Scala. Something like Haskell’s newtype
simply doesn’t. This is why people like @smarter are right in being careful in pushing this pattern.