Scala中的动态属性

时间:2011-03-20 02:03:48

标签: reflection scala dynamic

Scala是否支持动态属性?例如:

val dog = new Dynamic // Dynamic does not define 'name' nor 'speak'.
dog.name = "Rex" // New property.
dog.speak = { "woof" } // New method.

val cat = new Dynamic
cat.name = "Fluffy"
cat.speak = { "meow" }

val rock = new Dynamic
rock.name = "Topaz"
// rock doesn't speak.

def test(val animal: Any) = {
   animal.name + " is telling " + animal.speak()
}

test(dog) // "Rex is telling woof"
test(cat) // "Fluffy is telling meow"
test(rock) // "Topaz is telling null"

我们可以在Scala中获得的最接近的东西是什么?如果有类似“addProperty”的东西允许像普通字段一样使用添加的属性,那就足够了。

我对结构类型声明(“type safe duck typing”)不感兴趣。我真正需要的是在运行时添加新的属性和方法,以便该对象可以被期望添加的元素存在的方法/代码使用。

3 个答案:

答案 0 :(得分:8)

Scala 2.9将具有特殊处理的动态特性,可能正是您所寻找的。

这个博客有很大的关注:http://squirrelsewer.blogspot.com/2011/02/scalas-upcoming-dynamic-capabilities.html

我猜想在invokeDynamic方法中你需要检查“name_ =”,“speak_ =”,“name”和“speak”,你可以将值存储在私有地图中。

答案 1 :(得分:3)

我想不出真的需要在运行时动态添加/创建方法/属性的原因,除非动态标识符也被允许 - 和/或 - 与外部动态源的神奇绑定(JRuby或JSON是两个很好的例子)。

否则,发布的示例可以完全使用Scala中现有的静态类型通过“匿名”类型和结构类型实现。无论如何,并不是说“动态”不方便(并且正如0__指出的那样,即将到来 - 随意“走向边缘”; - )。

考虑:

val dog = new {
   val name = "Rex"
   def speak = { "woof" }
}

val cat = new {
   val name = "Fluffy"
   def speak = { "meow" }
}

// Rock not shown here -- because it doesn't speak it won't compile
// with the following unless it stubs in. In both cases it's an error:
// the issue is when/where the error occurs.

def test(animal: { val name: String; def speak: String }) = {
   animal.name + " is telling " + animal.speak
}

// However, we can take in the more general type { val name: String } and try to
// invoke the possibly non-existent property, albeit in a hackish sort of way.
// Unfortunately pattern matching does not work with structural types AFAIK :(

val rock = new {
   val name = "Topaz"
}

def test2(animal: { val name: String }) = {
   animal.name + " is telling " + (try {
       animal.asInstanceOf[{ def speak: String }).speak
   } catch { case _ => "{very silently}" })
}

test(dog)
test(cat)
// test(rock) -- no! will not compile (a good thing)
test2(dog)
test2(cat)
test2(rock)

但是,这种方法很快就会变得很麻烦(“添加”创建新类型所需的新属性并将当前数据复制到其中)并且部分利用了示例代码的简单性。也就是说,以这种方式创建真正的“开放”对象实际上是不可能的;对于“开放”数据,在当前的Scala(2.8)实现中,各种Map可能是更好/可行的方法。

快乐的编码。

答案 2 :(得分:2)

首先,正如@pst指出的那样,您的示例可以使用静态类型完全实现,它不需要动态输入。

其次,如果您想用动态类型语言编程,请使用动态类型语言进行编程。

话虽这么说,你可以在Scala中实际执行类似的操作。这是一个简单的例子:

class Dict[V](args: (String, V)*) extends Dynamic {
  import scala.collection.mutable.Map

  private val backingStore = Map[String, V](args:_*)

  def typed[T] = throw new UnsupportedOperationException()

  def applyDynamic(name: String)(args: Any*) = {
    val k = if (name.endsWith("_=")) name.dropRight(2) else name
    if (name.endsWith("_=")) backingStore(k) = args.first.asInstanceOf[V]
    backingStore.get(k)
  }

  override def toString() = "Dict(" + backingStore.mkString(", ") + ")"
}

object Dict {
  def apply[V](args: (String, V)*) = new Dict(args:_*)
}

val t1 = Dict[Any]()
t1.bar_=("quux")

val t2 = new Dict("foo" -> "bar", "baz" -> "quux")
val t3 = Dict("foo" -> "bar", "baz" -> "quux")

t1.bar // => Some(quux)
t2.baz // => Some(quux)
t3.baz // => Some(quux)

正如你所看到的,实际上你非常接近。你的主要错误是Dynamic是一个特征,而不是一个类,所以你不能实例化它,你必须混合它。你显然必须实际定义你想要它做什么,即实现{ {1}}和typed

如果您希望 示例正常工作,则会出现一些并发症。特别是,您需要类似安全的异类映射作为后备存储。此外,还有一些语法上的考虑因素。例如,如果applyDynamic存在foo.bar = bazfoo.bar_=(baz)只会被转换为foo.bar_=,而foo只是Dynamic对象。