Implementing ExpandoObject in Scala
Asked Answered
G

1

7

I am trying to implement C#'s ExpandoObject-like class in Scala. This is how it is supposed to work:

val e = new ExpandoObject
e.name := "Rahul" // This inserts a new field `name` in the object.
println(e.name) // Accessing its value.

Here is what I have tried so far:

implicit def enrichAny[A](underlying: A) = new EnrichedAny(underlying)
class EnrichedAny[A](underlying: A) {
  // ...
  def dyn = new Dyn(underlying.asInstanceOf[AnyRef])
}

class Dyn(x: AnyRef) extends Dynamic {
  def applyDynamic(message: String)(args: Any*) = {
    new Dyn(x.getClass.getMethod(message, 
      args.map(_.asInstanceOf[AnyRef].getClass) : _*).
      invoke(x, args.map(_.asInstanceOf[AnyRef]) : _*))
  }
  def typed[A] = x.asInstanceOf[A]
  override def toString = "Dynamic(" + x + ")"
}

class ExpandoObject extends Dynamic {
  private val fields = mutable.Map.empty[String, Dyn]
  def applyDynamic(message: String)(args: Any*): Dynamic = {
    fields get message match {
      case Some(v) => v
      case None => new Assigner(fields, message).dyn
    }
  }
}

class Assigner(fields: mutable.Map[String, Dyn], message: String) {
  def :=(value: Any): Unit = {
    fields += (message -> value.dyn)
  }
}

When I try to compile this code, I get a StackOverflowError. Please help me get this work. :) Thanks.

Giff answered 20/10, 2011 at 7:19 Comment(0)
G
4

Got it working after some playing around. The solution isn't typesafe though (which in this case does not matter, since the point of this little utility is to work around type system. :-)

trait ExpandoObject extends Dynamic with mutable.Map[String, Any] {
  lazy val fields = mutable.Map.empty[String, Any]
  def -=(k: String): this.type = { fields -= k; this }
  def +=(f: (String, Any)): this.type = { fields += f; this }
  def iterator = fields.iterator
  def get(k: String): Option[Any] = fields get k
  def applyDynamic(message: String)(args: Any*): Any = {
    this.getOrElse(message, new Assigner(this, message))
  }
}

implicit def anyToassigner(a: Any): Assigner = a match {
  case x: Assigner => x
  case _ => sys.error("Not an assigner.")
}

class Assigner(ob: ExpandoObject, message: String) {
  def :=(value: Any): Unit = ob += (message -> value)
}
Giff answered 20/10, 2011 at 13:37 Comment(3)
Could you give a usage example?Butyrin
In my particular case, I have to process some structured data in Excel sheets, which I know nothing about at compile time. Treating that data as language level records makes processing simpler.Giff
You can refer to the linked article above for more examples.Giff

© 2022 - 2024 — McMap. All rights reserved.