Question

E.g.:

def updateAsinRecords(asins:Seq[String], recordType:String)

Above method takes a Seq of ASINs and a record type. Both have type of String. There are also other values that are passed around with type String in the application. Needless to say, this being Scala, I'd like to use the type system to my advantage. How to pass around string values in a type safe manner (like below)?

def updateAsinRecords(asins:Seq[ASIN], recordType:RecordType)
                                ^                 ^

I can imagine, having something like this:

trait ASIN { val value:String }

but I'm wondering if there's a better approach...

Was it helpful?

Solution

There is an excellent bit of new Scala functionality know as Value Classes and Universal Traits. They impose no runtime overhead but you can use them to work in a type safe manner:

class AnsiString(val inner: String) extends AnyVal
class Record(val inner: String) extends AnyVal

def updateAnsiRecords(ansi: Seq[AnsiString], record: Record)

They were created specifically for this purpose.

OTHER TIPS

You could add thin wrappers with case classes:

case class ASIN(asin: String)
case class RecordType(recordType: String)

def updateAsinRecords(asins: Seq[ASIN], recordType: RecordType) = ???

updateAsinRecords(Vector(ASIN("a"), ASIN("b")), RecordType("c"))

This will not only make your code safer, but it will also make it much easier to read! The other big advantage of this approach is that refactoring later will be much easier. For example, if you decide later that an ASIN should have two fields instead of just one, then you just update the ASIN class definition instead of every place it's used. Likewise, you can do things like add methods to these types whenever you decide you need them.

In addition to the suggestions about using a Value Class / extends AnyVal, you should probably control the construction to allow only valid instances, since presumably not any old string is a valid ASIN. (And... is that an Amazon thing? It rings a bell somehow.)

The best way to do this is to make the constructor private and put a validating factory method in a companion object. The reason for this is that throwing exceptions in constructors (when an attempt is made to instantiate with an invalid argument) can lead to puzzling failure modes (I often see it manifest as a NoClassDefFoundError error when trying to load a different class).

So, in addition to:

case class ASIN private (asin: String) extends AnyVal { /* other stuff */ }

You should include something like this:

object A {
  import scala.util.{Try, Success, Failure}

  def fromString(str: String): Try[ASIN] =
    if (validASIN(str))
      Success(new ASIN(str))
    else
      Failure(new InvalidArgumentException(s"Invalid ASIN string: $str")
}

How about a type alias?

type ASIN = String

def update(asins: Seq[ASIN])

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top