Динамические свойства в Scala - PullRequest
2 голосов
/ 20 марта 2011

Поддерживает ли 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 ]

8 голосов
/ 20 марта 2011

Scala 2.9 будет иметь специально обработанную динамическую черту, которая может быть тем, что вы ищете.

Этот блог имеет большое значение: http://squirrelsewer.blogspot.com/2011/02/scalas-upcoming-dynamic-capabilities.html

Я бы предположил, что вМетод invokeDynamic вам нужно будет проверить на «name_ =», «speak_ =», «name» и «speak», и вы можете хранить значения в частной карте.

3 голосов
/ 20 марта 2011

Я не могу придумать причину для действительно необходимости динамически добавлять / создавать методы / свойства во время выполнения , если не разрешены динамические идентификаторы - и / или - магическиепривязка к внешнему динамическому источнику (два хороших примера - 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).

Счастливое кодирование.

2 голосов
/ 20 марта 2011

Во-первых, как указал @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 является признаком, а не классом, поэтому вы не можете создать его экземпляр, вам нужно смешать его. И вам, очевидно, нужно на самом деле определить, что вы хотите, то есть реализовать typed и applyDynamic.

Если вы хотите, чтобы ваш пример работал, есть пара сложностей. В частности, вам нужно что-то вроде разнородной карты с сохранением типов в качестве резервного хранилища. Также есть некоторые синтаксические соображения. Например, foo.bar = baz переводится только в foo.bar_=(baz), если существует foo.bar_=, чего нет, потому что foo является Dynamic объектом.

...