2009-10-13 3 views
4

Disons que jesucre pour la création d'Syntactic objet de compilation à Scala

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

Je veux permettre aux utilisateurs de déclarer rapidement de nouvelles instances de fooTrait avec leurs propres corps définis pour fooFn. Idéalement, je voudrais quelque chose comme

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

de travailler. Cependant, je ne peux pas le faire

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

parce que celui-ci utilise des fermetures, et donc des résultats dans différents objets lorsque le programme est exécuté plusieurs fois. Ce que je veux, c'est vraiment être capable d'obtenir le classOf de l'objet retourné par newFoo et ensuite le rendre constructible sur une machine différente. Que fais-je?

Si vous êtes intéressé par le cas d'utilisation, je suis en train d'écrire un wrapper Scala pour Hadoop qui vous permet d'exécuter

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

La chose au milieu doit être transformé en une classe implémente une interface particulière et peut ensuite être instanciée sur des machines différentes (en exécutant le même fichier jar) à partir du nom de la classe.

Notez que Scala fait la bonne chose avec le sucre syntaxique qui convertit (x: Int) => x + 5 en une instance de Function1. Ma question est de savoir si je peux reproduire cela sans pirater les internes de Scala. Si c'était lisp (comme je suis habitué), ce serait une macro de compilation triviale ...: sniff:

+0

Souhaitez-vous sérialiser la fonction sur une machine distante? Que voulez-vous dire en instanciant la classe "à partir du nom de classe". Quel est le nom de la classe ou de la classe dans cet exemple? –

+0

Fondamentalement, ce que je veux, c'est avoir du code Foo (Int => Int): String qui retourne quelque chose. Ensuite, sur une machine différente qui a le même fichier jar chargé, je veux exécuter Bar (s: String): FooTrait [Int] sur cette chaîne et avoir Bar (Foo (fn)) retourner un objet qui a fn comme méthode . Une façon de le faire est def Foo (obj: FooTrait [Int]) = classOf (obj) .toString et ensuite Bar créer une nouvelle instance de la classe à partir du nom de la classe, mais cela nécessite de passer dans une classe à Foo, pas un lambda. – bsdfish

Répondre

2

Voici une version qui correspond à la syntaxe de ce que vous listez dans la question et sérialise/exécute la fonction anon. Notez que cela sérialise l'état de l'objet Function2 afin que la version sérialisée puisse être restaurée sur une autre machine. Juste le nom de classe est insuffisant, comme illustré ci-dessous la solution.

Vous devriez créer votre propre fonction d'encodage/décodage, même si vous voulez simplement inclure votre propre implémentation Base64 (ne pas compter sur le Hotspot de 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 
*/ 

Pour vous convaincre que le nom de classe ne suffit pas à utiliser la fonction sur une autre machine, considérez le code ci-dessous qui crée 100 fonctions différentes. Comptez les classes sur le système de fichiers et comparez.

object App extends Application { 
    import SHadoopImports._ 

    for (i <- 1 to 100) { 
     IO(i + ": source") --> ((x:Int,y:Int)=>(x*i)+y) --> IO("sink") 
    } 
} 
+0

Merci beaucoup pour ça! Je suis entièrement d'accord avec vous que dans le contexte dont je parlais, le nom de classe était insignifiant. Cela semble vraiment prometteur! – bsdfish

2

Suggestion rapide: pourquoi n'essayez-vous pas de créer un objet FunctionN transformant def implicite le trait attendu par la méthode ->. J'espère que vous ne devrez utiliser aucune macro pour cela!

+0

C'est l'idée. Mais que devrait dire le def implicite? Tout ce que je peux penser est exactement ce que j'ai défini pour myFoo, et ce n'est pas la compilation mais l'exécution. – bsdfish