3

As I see, primitive types like String and Long cannot be extended as they are defined as final. But this is a pity for a type-safe approach. In code that does not revolve around e.g. string manipulation, I prefer to type my data rather than use String, Long, Int, etc: as long as I'm using a type safe language, I'd like my code to really be typed from the ground up.

As per experimentation and as demonstrated on a very old question type aliases do not seem to facilitate this. Currently, I will use things like:

case class DomainType(value: String)

at the cost of having to use .value where the value is needed.

Is there any other language feature been introduced after scala 2.8 or otherwise, that can neatly facilitate type safe sub-typed primitives? are there any object overrides that proxy the underlying value, but still let type matching occur?

Community
  • 1
  • 1
matanster
  • 15,072
  • 19
  • 88
  • 167
  • Totally agree. Actually it is worse than just `.value` boilerplate. I mean `DomainType` can't be used in the cases where `String` could be, you should reimplement json/sql serializers/deserializers, etc. – lambdas Mar 28 '15 at 10:50

3 Answers3

6

I don't agree with your way of thinking. Java primitives can't be extended because they are primitives (btw String is not a primitive). They are a direct representation of byte code types. Extending them would make no sense from the compiler perspective.

Implicit value classes

Scala deals with this using the pimp my library pattern, for example with the class RichInt. This permits to add new methods to an existing type (mainly) without object instantiation (through value classes and implicits). Please also have a look to implicit classes.

implicit class DomainType(val o: String) extends AnyVal {
  def myDomainMethod: Int = o.size
}

"hello".myDomainMethod // return 5

Problem, this doesn't allow you to restrict a type as you would like to do with your DomainType. But type classes can help you.

Type classes

Here we want to add a constraint to a type, without inheritance. As said in this link,

As such, type classes allow ad-hoc and retroactive polymorphism. Code that relies on type classes is open to extension without the need to create adapter objects.

The following example shows how the method foo only accepts an argument where an implicit of type DomainType[T] is in scope. It replaces the inheritance you wanted by a retroactive polymorphism. You also keep the benefits from a domain type: The intent is clear, the call type safe and you can add new domain methods.

trait DomainType[T] {
  def myDomainMethod(o: T): Int
}

object DomainType {
  implicit object StringDomainType extends DomainType[String] {
    def myDomainMethod(o: String): Int = o.size
  }
}

def foo[T : DomainType](p: T): Int = {
  implicitly[DomainType[T]].myDomainMethod(p)
}

foo("hello") // return 5
foo(5) // compilation exception

Case classes + implicits

If the only thing that annoys you with your DomainType case class is to call .value, you can always add an implicit that does it for you.

implicit def unwrapDomainType(o: DomainType): String = o.value

Of course this can lower the clarity of the code and I would not recommend it.

Lomig Mégard
  • 1,828
  • 14
  • 18
  • Thanks but maybe you can rewrite it to focus more on the solution rather than slamming the motivation. It is left quite unclear how this sequence accomplishes the use case. – matanster Mar 28 '15 at 12:03
  • Well I see here no solution. Nice narrative, but it goes pretty much around the question. The `implicit def` doesn't have the desired effect - once you have _two or more_ domain types mapped to say, `String`, it can only implicitly capture for _one_ of them, or did you somehow mean it integrates with the first code snippet?!? – matanster Mar 29 '15 at 13:52
2

For the primitive types, you could use the Value Classes. They do not extend the primitive types, but they are internally represented by the JVM primitives and so they avoid in most situations the runtime overhead. You can find more information here.

Gregor Raýman
  • 3,051
  • 13
  • 19
0

It seems Tagged types form scalaz would be a good fit.

val a: Int @@ DomainType = Tag(5)

For more information about the usage look at this excellent series of articles: http://eed3si9n.com/learning-scalaz/Tagged+type.html Unfortunately it seems that one has to explicitly call unwrap from scalaz 7.1 onwards but with scalaz7 it was possible to just call something like:

a * 5

But depending on your needs this may still be useful

matthias
  • 356
  • 3
  • 3