How to mix-in a trait to instance?

前端 未结 5 1388
日久生厌
日久生厌 2020-12-04 12:04

Given a trait MyTrait:

trait MyTrait {
  def doSomething = println(\"boo\")
}

it can be mixed into a class with extends<

相关标签:
5条回答
  • 2020-12-04 12:34

    Why not use Scala's extend my library pattern?

    https://alvinalexander.com/scala/scala-2.10-implicit-class-example

    I'm not sure what the return value is of:

    var o = DBHelper.loadMyEntityFromDB(primaryKey);

    but let us say, it is DBEntity for our example. You can take the class DBEntity and convert it to a class that extends your trait, MyTrait.

    Something like:

    trait MyTrait {
      def doSomething = {
        println("boo")
      }
    }
    
    class MyClass() extends MyTrait
    
    // Have an implicit conversion to MyClass
    implicit def dbEntityToMyClass(in: DBEntity): MyClass = 
    new MyClass()
    

    I believe you could also simplify this by just using an implicit class.

    implicit class ConvertDBEntity(in: DBEntity) extends MyTrait
    

    I particularly dislike the accepted answer here, b/c it overloads the :: operator to mix-in a trait.

    In Scala, the :: operator is used for sequences, i.e.:

    val x = 1 :: 2 :: 3 :: Nil
    

    Using it as a means of inheritance feels, IMHO, a little awkward.

    0 讨论(0)
  • 2020-12-04 12:36

    I usually used a implicit to mix in a new method to an existing object.

    See, if I have some code as below:

    final class Test {
      def f = "Just a Test"
      ...some other method
    }
    trait MyTrait {
      def doSomething = {
        println("boo")
      }
    }
    object HelperObject {
      implicit def innerObj(o:MixTest) = o.obj
    
      def mixWith(o:Test) = new MixTest(o)
      final class MixTest private[HelperObject](obj:Test) extends MyTrait
    }
    

    and then you can use MyTrait method with an already existing object Test.

    val a = new Test
    import HelperObject._
    val b = HelperObject.mixWith(a)
    println(b.f)
    b.doSomething
    

    in your example, you can use like this:

    import HelperObject._
    val o = mixWith(DBHelper.loadMyEntityFromDB(primaryKey));
    o.doSomething
    

    I am thinking out a prefect syntax to define this HelperObject:

    trait MyTrait {
      ..some method
    }
    object MyTrait {
      implicit def innerObj(o:MixTest) = o.obj
    
      def ::(o:Test) = new MixTest(o)
      final class MixTest private[MyTrait](obj:Test) extends MyTrait
    }
    //then you can use it
    val a = new Test
    val b = a :: MyTrait
    b.doSomething
    b.f
    // for your example
    val o = DBHelper.loadMyEntityFromDB(primaryKey) :: MyTrait
    o.doSomething
    
    0 讨论(0)
  • 2020-12-04 12:42

    An existing runtime object in the JVM has a certain size on the heap. Adding a trait to it would mean altering its size on the heap, and changing its signature.

    So the only way to go would be to do some kind of transformation at compile time.

    Mixin composition in Scala occurs at compile time. What compiler could potentially do is create a wrapper B around an existing object A with the same type that simply forwards all calls to the existing object A, and then mix in a trait T to B. This, however, is not implemented. It is questionable when this would be possible, since the object A could be an instance of a final class, which cannot be extended.

    In summary, mixin composition is not possible on existing object instances.

    UPDATED:

    Related to the smart solution proposed by Googol Shan, and generalizing it to work with any trait, this is as far as I got. The idea is to extract the common mixin functionality in the DynamicMixinCompanion trait. The client should then create a companion object extending DynamicMixinCompanion for each trait he wants to have the dynamic mixin functionality for. This companion object requires defining the anonymous trait object gets created (::).

    trait DynamicMixinCompanion[TT] {                                                                    
      implicit def baseObject[OT](o: Mixin[OT]): OT = o.obj                                              
    
      def ::[OT](o: OT): Mixin[OT] with TT                                                               
      class Mixin[OT] protected[DynamicMixinCompanion](val obj: OT)                                      
    }                                                                                                    
    
    trait OtherTrait {                                                                                   
      def traitOperation = println("any trait")                                                          
    }                                                                                                    
    
    object OtherTrait extends DynamicMixinCompanion[OtherTrait] {                                        
      def ::[T](o: T) = new Mixin(o) with OtherTrait                                                     
    }                                                                                                    
    
    object Main {                                                                                        
      def main(args: Array[String]) {                                                                    
        val a = "some string"                                                                            
        val m = a :: OtherTrait                                                                          
        m.traitOperation                                                                                 
        println(m.length)                                                                                
      }                                                                                                  
    }                                                                                                    
    
    0 讨论(0)
  • 2020-12-04 12:49

    I have a idea for this usage:

    //if I had a class like this
    final class Test {
      def f = println("foo")
    }
    trait MyTrait {
      def doSomething = {
        println("boo")
      }
    }
    object MyTrait {
      implicit def innerObj(o:MixTest) = o.obj
    
      def ::(o:Test) = new MixTest(o)
      final class MixTest private[MyTrait](val obj:Test) extends MyTrait
    }
    

    you can use this trait as below:

    import MyTrait._
    
    val a = new Test
    val b = a :: MyTrait
    b.doSomething
    b.f
    

    for your example code:

    val o = DBHelper.loadMyEntityFromDB(primaryKey) :: MyTrait
    o.doSomething
    

    I hope this can help you.

    UPDATED

    object AnyTrait {
      implicit def innerObj[T](o: MixTest[T]):T = o.obj
    
      def ::[T](o: T) = new MixTest(o)
      final class MixTest[T] private[AnyTrait](val obj: T) extends MyTrait
    }
    

    but this pattern has some restrict, you can't use some implicit helper method that defined already.

    val a = new Test
    a.f
    val b = a :: AnyTrait
    b.f1
    b.f
    val c = "say hello to %s" :: AnyTrait
    println(c.intern)  // you can invoke String's method 
    println(c.format("MyTrait"))  //WRONG. you can't invoke StringLike's method, though there defined a implicit method in Predef can transform String to StringLike, but implicit restrict one level transform, you can't transform MixTest to String then to StringLike.
    c.f1
    val d = 1 :: AnyTrait
    println(d.toLong)
    d.toHexString // WRONG, the same as above
    d.f1
    
    0 讨论(0)
  • 2020-12-04 12:50

    What about an implicit class? It seems easier to me compared to the way in the other answers with a final inner class and a "mixin"-function.

    trait MyTrait {
    
        def traitFunction = println("trait function executed")
    
    }
    
    class MyClass {
    
        /**
         * This inner class must be in scope wherever an instance of MyClass
         * should be used as an instance of MyTrait. Depending on where you place
         * and use the implicit class you must import it into scope with
         * "import mypackacke.MyImplictClassLocation" or
         * "import mypackage.MyImplicitClassLocation._" or no import at all if
         * the implicit class is already in scope.
         * 
         * Depending on the visibility and location of use this implicit class an
         * be placed inside the trait to mixin, inside the instances class,
         * inside the instances class' companion object or somewhere where you
         * use or call the class' instance with as the trait. Probably the
         * implicit class can even reside inside a package object. It also can be
         * declared private to reduce visibility. It all depends on the structure
         * of your API.
         */
        implicit class MyImplicitClass(instance: MyClass) extends MyTrait
    
        /**
         * Usage
         */
        new MyClass().traitFunction
    
    }
    
    0 讨论(0)
提交回复
热议问题