Синтаксический сахар для создания объектов во время компиляции в Scala - PullRequest
4 голосов
/ 13 октября 2009

Допустим, у меня есть

trait fooTrait[T] {
  def fooFn(x: T, y: T) : T 
}

Я хочу, чтобы пользователи могли быстро объявлять новые экземпляры fooTrait со своими собственными определенными телами для fooFn. В идеале я бы хотел что-то вроде

val myFoo : fooTrait[T] = newFoo((x:T, y:T) => x+y) 

на работу. Тем не менее, я не могу просто сделать

def newFoo[T](f: (x:T, y:T) => T) = new fooTrait[T] { def fooFn(x:T, y:T):T = f(x,y); }

, потому что при этом используются замыкания, и поэтому при многократном запуске программы создаются разные объекты. Что мне действительно нужно , так это чтобы я мог получить classOf объекта, возвращаемого newFoo, и затем создать его на другом компьютере. Что мне делать?

Если вас интересует сценарий использования, я пытаюсь написать оболочку Scala для Hadoop, которая позволяет выполнять

IO("Data") --> ((x: Int, y: Int) => (x, x+y)) --> IO("Out")

Объект в середине должен быть превращен в класс, который реализует определенный интерфейс, а затем может быть создан на разных машинах (выполняя один и тот же файл JAR) только из имени класса.

Обратите внимание, что Scala правильно делает с синтаксическим сахаром, который преобразует (x: Int) => x + 5 в экземпляр Function1. Мой вопрос заключается в том, могу ли я повторить это, не взломав внутренности Scala. Если бы это был lisp (как я привык), это был бы тривиальный макрос времени компиляции ...: sniff:

Ответы [ 2 ]

2 голосов
/ 16 октября 2009

Вот версия, которая соответствует синтаксису того, что вы перечислили в вопросе, и сериализует / выполняет функцию anon. Обратите внимание, что это сериализует состояние объекта Function2, так что сериализованная версия может быть восстановлена ​​на другом компьютере. Недостаточно только имени класса, как показано ниже.

Вы должны создать свою собственную функцию кодирования / декодирования, если даже просто включить свою собственную реализацию Base64 (не полагаясь на горячую точку Sun).

object SHadoopImports {
    import java.io._

    implicit def functionToFooString[T](f:(T,T)=>T) = {
        val baos = new ByteArrayOutputStream()
        val oo = new ObjectOutputStream(baos)
        oo.writeObject(f)
        new sun.misc.BASE64Encoder().encode(baos.toByteArray())
    }

    implicit def stringToFun(s: String) = {
        val decoder = new sun.misc.BASE64Decoder();
        val bais = new ByteArrayInputStream(decoder.decodeBuffer(s))
        val oi = new ObjectInputStream(bais)  
        val f = oi.readObject()
        new {
            def fun[T](x:T, y:T): T = f.asInstanceOf[Function2[T,T,T]](x,y)
        }
    }
}

// I don't really know what this is supposed to do
// just supporting the given syntax
case class IO(src: String) {
    import SHadoopImports._
    def -->(s: String) = new {
        def -->(to: IO) = {
            val IO(snk) = to
            println("From: " + src)
            println("Applying (4,5): " + s.fun(4,5))
            println("To: " + snk)
        }
    }
}

object App extends Application {
  import SHadoopImports._

  IO("MySource") --> ((x:Int,y:Int)=>x+y) --> IO("MySink")
  println
  IO("Here") --> ((x:Int,y:Int)=>x*y+y) --> IO("There")
}

/*
From: MySource
Applying (4,5): 9
To: MySink

From: Here
Applying (4,5): 25
To: There
*/

Чтобы убедиться, что имени класса недостаточно для использования функции на другом компьютере, рассмотрите код ниже, который создает 100 различных функций. Подсчитайте классы в файловой системе и сравните.

object App extends Application {
  import SHadoopImports._

  for (i <- 1 to 100) {
      IO(i + ": source") --> ((x:Int,y:Int)=>(x*i)+y) --> IO("sink")
  }
}
2 голосов
/ 13 октября 2009

Быстрое предложение: почему бы вам не попытаться создать неявное def, преобразовывающее объект FunctionN в черту, ожидаемую методом ->.

Надеюсь, вам не понадобится макрос для этого!

...