1

I am writing simple variable system in Scala. I need a way to nicely hold these AnyVars and access them by their string names. I have this object called context to manage it but it is really just a wrapper for HashMap[String, AnyVar]. I want to find a nicer way to do this.

class Context {
  import scala.collection.mutable.HashMap
  import scala.reflect.ClassTag

  private var variables = HashMap[String, AnyVal] ()

  def getVariable (name: String):AnyVal = variables(name)
  def setVariable (name: String, value: AnyVal) = variables += ((name, value))

  def getVariableOfType[T <: AnyVal : ClassTag] (name:String):T ={
    val v = variables(name)
    v match {
      case x: T => x
      case _ => null.asInstanceOf[T]
    }
  }
}

I really want an implementation that is type safe

0__
  • 66,707
  • 21
  • 171
  • 266
Vogon Jeltz
  • 1,166
  • 1
  • 14
  • 31
  • 2
    Define "nicer". It is unclear what you are asking. – 0__ Aug 17 '15 at 20:01
  • @0__ Any scala code that uses .asInstanceOf[T] is not type safe. – Vogon Jeltz Aug 17 '15 at 20:06
  • 2
    You can't say you want to implement accessing of `AnyVal`s using `String`s and also say you ant it to be type safe in the same sentence ;) What exactly is your larger goal here? – LuxuryMode Aug 17 '15 at 20:09
  • @VogonJeltz, in your case it is typesafe, because you already know which object is casted to `T` - `null`. There will not occur ClassCastExcception. But greater problem is that you return `null` and your code throws exceptions if the key isn't present in the map. – ka4eli Aug 17 '15 at 20:10
  • @LuxuryMode good point. I want to be able to store any value with a string reference. It doesn't need to be a HashMap but that is a holder solution – Vogon Jeltz Aug 17 '15 at 20:11
  • @ka4eli I want a way to hold these variables without using `.asInstanceOf[T]` – Vogon Jeltz Aug 17 '15 at 20:13
  • @VogonJeltz Same point applies. If you want "any value" then you're talking about `AnyVal` in which case you know nothing more about the object other than that is is an `AnyVal`. There's no way to refine the types further than that without resorting to `asInstanceOf` and other non-type safe methods. – LuxuryMode Aug 17 '15 at 20:18

2 Answers2

4

You are defining the upper bound of your variables as AnyVal. These are mostly primitive types such as Int, Boolean etc. not reference types for which null makes sense. Therefore you have to include a cast to T. This doesn't give you a null reference but the default value for that value type, such as 0 for Int, false for Boolean:

null.asInstanceOf[Int]      // 0
null.asInstanceOf[Boolean]  // false

So this has nothing to do with type-safety but just the fact that Scala would otherwise refuse to use a null value here. Your pattern match against x: T is already type-safe.

A better approach would be to return an Option[T] or to throw a NoSuchElementException if you try to query a non-existing variable or the wrong type.

Be aware that using a plain hash-map without synchronisation means your Context is not thread safe.

0__
  • 66,707
  • 21
  • 171
  • 266
  • Are you saying you can call a method on `null` ? – Dici Aug 17 '15 at 20:20
  • @Dici No. Calling a method on `null` will always result in a `NullPointerException`. But there is no `null` for an `AnyVal`. In Java, an uninitialised `Int` has value `0`, an uninitialised `Boolean` is `false` etc. – 0__ Aug 17 '15 at 20:21
  • `null.isIstanceOf[Int]` will produce a `NullPointerException`. I cannot test it on the device I'm using right now, but any other behaviour would suprise me – Dici Aug 17 '15 at 20:22
  • 1
    Well then let yourself be surprised :) Note that `asInstanceOf` is written as a method but translates to a cast on the JVM. – 0__ Aug 17 '15 at 20:23
  • I believe you then... do you think it should be this way ? Personally, I don't, it does not make sense. – Dici Aug 17 '15 at 20:24
2

Pattern matching with abstract types is difficult, and more so with instances of AnyVal. I found an approach which works at ClassTag based pattern matching fails for primitives

In general for your problem, I would simply use a Map[String, AnyVal] with an implicit extension to give you getOfType. Then you can use the existing methods to put and get variables as you wish. You can use a type alias to give your type a more meaningful reference.

object Context {

  import scala.reflect.ClassTag
  import scala.runtime.ScalaRunTime

  type Context = Map[String, AnyVal]

  def apply(items: (String, AnyVal)*): Context = items.toMap

  implicit class ContextMethods(c: Context) {

    class MyTag[A](val t: ClassTag[A]) extends ClassTag[A] {
      override def runtimeClass = t.runtimeClass
      override def unapply(x: Any): Option[A] = 
        if (t.runtimeClass.isPrimitive && (ScalaRunTime isAnyVal x) &&
          (x.getClass getField "TYPE" get null) == t.runtimeClass)
          Some(x.asInstanceOf[A])
        else t unapply x

    }

    def getOfType[T <: AnyVal](key: String)(implicit t: ClassTag[T]): Option[T] = {
      implicit val u = new MyTag(t)
      c.get(key).flatMap {
          case x: T => Some(x: T)
          case _ => None
      }
    }
  }
}

With this stuff collected together in an object which you can import, use of it becomes straightforward:

import Context._

val context = Context(
  "int_var" -> 123,
  "long_var" -> 456l,
  "double_var" -> 23.5d
)

context.get("int_var")//Some(123)
context.get("long_var")//Some(456)

context.getOfType[Int]("int_var")//Some(123)
context.getOfType[Double]("double_var")//Some(23.5)
context.getOfType[Int]("double_var")//None
Community
  • 1
  • 1
mattinbits
  • 10,370
  • 1
  • 26
  • 35