2010-07-15 22 views
39

Elde etmek istediğim şey,Scala'daki dinamik mixin - mümkün mü?

def dynamix[A, B](a: A): A with B 

için düzgün bir uygulamaya sahip olmaktır B nin ne olduğunu bilebilirim, fakat A'nın ne olduğunu bilmiyorum (fakat eğer B kendinden bir tipse, o zaman bazı kısıtlamalar ekleyebilirim) A). Scala derleyici yukarıdaki imzayla mutludur, ancak uygulamanın nasıl görüneceğini henüz anlamadım - eğer mümkün ise.

Aklıma gelen bazı seçenekler:

  • Yansıma/dinamik proxy kullanma.
    • En basit durum: A, Java düzeyindeki bir arabirimdir + B'yi başlatabilir ve kendi türü yoktur. Sanırım çok zor olmayacaktı (bazı kötü, beklenmedik problemler yaşamadığım sürece):
      yeni bir B (b) oluşturmak, ayrıca hem A hem de B'yi uygulayan bir proxy oluşturmak ve hem a hem de b. .
    • Eğer B örneklenemezse, hala bir alt sınıfı oluşturabilir ve yukarıda açıklandığı gibi yapar. Kendine ait bir türü varsa, muhtemelen burada ve burada bazı delegasyonlara ihtiyacım var, ama yine de işe yarayabilir.
    • Peki ya A beton tipi ve bunun için uygun bir arayüz bulamıyorum?
    • Daha fazla sorunla karşılaşabilir miyim (örneğin, doğrusallaştırma ile ilgili bir şey veya Java birlikte çalışabilirliğine yardımcı olan özel yapılar)?
  • Bir mixin yerine bir tür sargı kullanarak ve B [A] ile geri dönülürken, b'ye erişilebilir.
    Ne yazık ki, bu durumda arayanın yuvalama işleminin nasıl yapıldığını bilmesi gerekecek, bu da karıştırma/sarma işleminin birkaç kez yapıldığında (D [C [B [A]]]) olması gerektiği gibi oldukça rahatsız edici olabilir. Gerekli işlevselliğe erişmek için doğru yuvalama seviyesini bulun, bu yüzden bir çözüm olarak düşünmüyorum.
  • Derleyici eklentisini uygulama. Onunla sıfır deneyimim var ama bağırsak hislerim önemsiz değil. Bence Kevin Wright'ın autoproxy eklentisi biraz benzer bir hedefe sahip, ama benim problemim için yeterli değil (henüz?).

Çalışabilecek başka fikirleriniz var mı? Hangi yolu önerirsiniz? Ne tür "zorluklar" beklemek?
Ya da unutmam gerekiyor, çünkü mevcut Scala kısıtlamaları ile mümkün değil mi?

Sorunumun ardındaki niyet: İş akışım olduğunu söyle, ama çok katı değil. Bazı adımlar sabit bir düzene sahiptir, ancak diğerleri yoktur, ama sonunda bunların hepsi (ya da daha fazla işlem için gerekli olan) yapılmalıdır.
Biraz daha somut örnek: Bir A'm var, B ve C'yi ekleyebilirim. İlk önce hangisinin yapıldığını umursamıyorum, ama sonunda C ile birlikte bir A'ya ihtiyacım var

Yorum: Groovy hakkında çok fazla bilgim yok ama SO this question kadar patladı ve sanırım En azından kavramsal olarak istediğim kadar ya da çok aynı.

+3

başka bir yaklaşım (canlı eğer yeterince iyice düşünmediğini bilmek): Bir 'Dynamix içinde Wrap [A, B]' (veya Dynamix [A, Dynamix [B, C]] 'yi iç içe geçirin ve istemci kodunu bilişsel olarak boşaltmak için örtüşmeyle açın. Her yuvalama derinliği tanımlanmalıdır (keyfi derinlik yoktur). Dezavantajlı: Kendinden tür kısıtlamaları zorlamıyor ya da türler arası etkileşime kendiliğinden tipler üzerinden izin vermiyor. İyi hile için –

+0

+1, ama yine de en azından bir problemle karşılaşacağım hissine sahibim: Sonunda C veya Dynamix ile bir A'nın B'yi isteyeceğini nasıl belirleyeceğim [A, Dynamix [B, C] ] ama karıştırma emrini umursamıyorum? Dynamix [A, Dynamix [C, B]] ile de mutlu olurum ve bence bu tür bir kısıtlama bulmak çok kolay olmayacaktır (özellikle karıştırılacak birçok özellik var). Yine de, yorumunuzu beğendim, bunun için teşekkürler. –

+0

olası bir kopyası [Dinamik olarak bir özellik karıştırma] (http://stackoverflow.com/questions/10373318/mixing-in-a-trait-dynamically) –

cevap

25

Bunun tam anlamıyla çalışma zamanında yapılmasının imkansız olduğuna inanıyorum çünkü özellikler yeni Java sınıflarına derleme zamanında karışıyor.Varolan bir sınıf ile bir özelliği karıştırırsanız anonim anonim bir ad-parçalanmış sınıf scalać tarafından oluşturulduğunu, classfiles bakarak ve javap kullanarak görebilirsiniz:

class Foo { 
    def bar = 5 
} 

trait Spam { 
    def eggs = 10 
} 

object Main { 
    def main(args: Array[String]) = { 
    println((new Foo with Spam).eggs) 
    } 
} 

scalac Mixin.scala; ls *.class getiri

Foo.class Main$.class Spam$class.class Main$$anon$1.class Main.class Spam.class

iken javap Main\$\$anon\$1 döner

Compiled from "mixin.scala" 

public final class Main$$anon$1 extends Foo implements Spam{ 
    public int eggs(); 
    public Main$$anon$1(); 
} 

Gördüğünüz gibi, scalać yeni bir anonim sınıfını oluşturur bu çalışma zamanında yüklenir; Muhtemelen bu anonim sınıfta eggs yöntemi Spam$class bir örneğini oluşturur ve üzerinde eggs çağırır, ancak tamamen emin değilim. Eğer Scala derleyici kullanmak gerek AFAIK, bu almak için yapabileceği en temiz çözüm muhtemelen yakın olduğu için

import scala.tools.nsc._; 
import scala.reflect.Manifest 

object DynamicClassLoader { 
    private var id = 0 
    def uniqueId = synchronized { id += 1; "Klass" + id.toString } 
} 

class DynamicClassLoader extends 
    java.lang.ClassLoader(getClass.getClassLoader) { 
    def buildClass[T, V](implicit t: Manifest[T], v: Manifest[V]) = { 

    // Create a unique ID 
    val id = DynamicClassLoader.uniqueId 

    // what's the Scala code we need to generate this class? 
    val classDef = "class %s extends %s with %s". 
     format(id, t.toString, v.toString) 

    println(classDef) 

    // fire up a new Scala interpreter/compiler 
    val settings = new Settings(null) 
    val interpreter = new Interpreter(settings) 

    // define this class 
    interpreter.compileAndSaveRun("<anon>", classDef) 

    // get the bytecode for this new class 
    val bytes = interpreter.classLoader.getBytesForClass(id) 

    // define the bytecode using this classloader; cast it to what we expect 
    defineClass(id, bytes, 0, bytes.length).asInstanceOf[Class[T with V]] 
    } 

} 


val loader = new DynamicClassLoader 

val instance = loader.buildClass[Foo, Spam].newInstance 
instance.bar 
// Int = 5 
instance.eggs 
// Int = 10 

: Ancak

, biz burada oldukça hacky hile yapabilirsiniz bu. Oldukça yavaş, ancak hafızaya alma muhtemelen büyük ölçüde yardımcı olacaktır. Bu yaklaşım oldukça saçma, saçma ve dilin tahılına karşı gelir. Her türlü tuhaf böceklerin girebileceğini hayal ediyorum; Java'yı benden daha uzun süre kullanan insanlar, sınıf yükleyicileriyle uğraşarak gelen çılgınlık konusunda uyarıyorlar.

+2

Özellikle sınıf yükünün önemsiz ve/veya oynaması mümkün olmayan bağlamlarda dezavantajları tamamen kabul ediyorum.Dürüst olmak gerekirse, daha temiz bir çözüm bekledim, ancak var olup olmadığından da emin değildim. Yine de daha iyi bir cevap yoktu ve muhtemelen işe yarayacaktı => kabul edildi. Bunun için çok teşekkürler. –

3
benim Bahar uygulama bağlamında Scala fasulye inşa etmek mümkün istedim, ama ben de inşa fasulye dahil edilecek katmalar belirtmek mümkün istedim

:

<?xml version="1.0" encoding="UTF-8"?> 
<beans xmlns="http://www.springframework.org/schema/beans" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xmlns:context="http://www.springframework.org/schema/context" 
    xmlns:scala="http://www.springframework.org/schema/scala" 
    xsi:schemaLocation=...> 

    <scala:bean class="org.cakesolutions.scala.services.UserService" > 
    <scala:with trait="org.cakesolutions.scala.services.Mixin1" /> 
    <scala:with trait="org.cakesolutions.scala.services.Mixin2" /> 

    <scala:property name="dependency" value="Injected" /> 
    <scala:bean> 
</beans> 

zorluk o Sınıfı olduğunu .forName işlevi miksleri belirtmeme izin vermiyor. Sonunda, yukarıdaki hacky çözümü Scala 2.9.1'e uzattım. Yani, burada onun tam gory var; İlkbaharın bitleri dahil.

class ScalaBeanFactory(private val beanType: Class[_ <: AnyRef], 
         private val mixinTypes: Seq[Class[_ <: AnyRef]]) { 
    val loader = new DynamicClassLoader 
    val clazz = loader.buildClass(beanType, mixinTypes) 

    def getTypedObject[T] = getObject.asInstanceOf[T] 

    def getObject = { 
    clazz.newInstance() 
    } 

    def getObjectType = null 
    def isSingleton = true 

object DynamicClassLoader { 
    private var id = 0 
    def uniqueId = synchronized { id += 1; "Klass" + id.toString } 
} 

class DynamicClassLoader extends java.lang.ClassLoader(getClass.getClassLoader) { 

    def buildClass(t: Class[_ <: AnyRef], vs: Seq[Class[_ <: AnyRef]]) = { 
    val id = DynamicClassLoader.uniqueId 

    val classDef = new StringBuilder 

    classDef.append("class ").append(id) 
    classDef.append(" extends ").append(t.getCanonicalName) 
    vs.foreach(c => classDef.append(" with %s".format(c.getCanonicalName))) 

    val settings = new Settings(null) 
    settings.usejavacp.value = true 
    val interpreter = new IMain(settings) 


    interpreter.compileString(classDef.toString()) 


    val r = interpreter.classLoader.getResourceAsStream(id) 
    val o = new ByteArrayOutputStream 
    val b = new Array[Byte](16384) 
    Stream.continually(r.read(b)).takeWhile(_ > 0).foreach(o.write(b, 0, _)) 
    val bytes = o.toByteArray 

    defineClass(id, bytes, 0, bytes.length) 
    } 

} 

kod henüz parametrelerle kurucular ile başa çıkamaz ve ebeveyn sınıfının yapıcı (o yapmalıyım?) Ek notların kopyalamaz. Ancak, bize scala Spring ad alanında kullanılabilen iyi bir başlangıç ​​noktası verir. Tabii ki, sadece bunun için benim sözüme bir Specs2 şartnamede saymaya ne:

class ScalaBeanFactorySpec extends Specification { 

    "getTypedObject mixes-in the specified traits" in { 
    val f1 = new ScalaBeanFactory(classOf[Cat], 
            Seq(classOf[Speaking], classOf[Eating])) 

    val c1 = f1.getTypedObject[Cat with Eating with Speaking] 

    c1.isInstanceOf[Cat with Eating with Speaking] must_==(true) 

    c1.speak // in trait Speaking 
    c1.eat  // in trait Eating 
    c1.meow  // in class Cat 
    } 

} 
İlgili konular