The Artima Developer Community
Sponsored Link

Weblogs Forum
Mixins2

16 replies on 2 pages. Most recent reply: Jan 4, 2006 12:05 AM by Cleo Saulnier

Welcome Guest
  Sign In

Go back to the topic listing  Back to Topic List Click to reply to this topic  Reply to this Topic Click to search messages in this forum  Search Forum Click for a threaded view of the topic  Threaded View   
Previous Topic   Next Topic
Flat View: This topic has 16 replies on 2 pages [ 1 2 | » ]
Bruce Eckel

Posts: 875
Nickname: beckel
Registered: Jun, 2003

Mixins2 (View in Weblogs)
Posted: Oct 20, 2005 3:08 PM
Reply to this message Reply
Summary
Someone pointed out that Josh Bloch suggests using interfaces to produce the effects of mixins; this is a common pointer when justifying the lack of multiple inheritance in Java. I've translated yesterday's C++ mixin example based on this idea.
Advertisement
Someone also suggested that the Java solution to the mixin problem is AOP, to which I generally agree. But in the absence of AOP, what's the best way we can do it?

Here's my first cut at the solution, but it seems very verbose and a bit awkward, especially compared to the elegance of the C++ solution. Perhaps people have ideas for improvements.


//: generics/Mixins.java
import java.util.*;

interface TimeStamped {
  long getStamp();
}

class TimeStampedImp implements TimeStamped {
  private final long timeStamp;
  public TimeStampedImp() {
    timeStamp = new Date().getTime();
  }
  public long getStamp() { return timeStamp; }
}

interface SerialNumbered {
  long getSerialNumber();
}

class SerialNumberedImp implements SerialNumbered {
  private static long counter = 1;
  private final long serialNumber = counter++;
  public long getSerialNumber() { return serialNumber; }
}

class Basic {
  private String value;
  public void set(String val) { value = val; }
  public String get() { return value; }
}

class Mixin extends Basic
implements TimeStamped, SerialNumbered {
  private TimeStamped timeStamp = new TimeStampedImp();
  private SerialNumbered serialNumber =
    new SerialNumberedImp();
  public long getStamp() { return timeStamp.getStamp(); }
  public long getSerialNumber() {
    return serialNumber.getSerialNumber();
  }
}

public class Mixins {
  public static void main(String[] args) {
    Mixin mixin1 = new Mixin(), mixin2 = new Mixin();
    mixin1.set("test string 1");
    mixin2.set("test string 2");
    System.out.println(mixin1.get() + " " +
      mixin1.getStamp() +  " " + mixin1.getSerialNumber());
    System.out.println(mixin2.get() + " " +
      mixin2.getStamp() +  " " + mixin2.getSerialNumber());
  }
} /* Output:
test string 1 1129845639046 1
test string 2 1129845639046 2
*///:~


Bob Lee

Posts: 4
Nickname: crazybob
Registered: Oct, 2005

Re: Mixins2 Posted: Oct 20, 2005 5:25 PM
Reply to this message Reply
Bruce, have you tried Dynaop (http://dynaop.dev.java.net/) yet? Dynaop will generate your "Mixins" class for you at runtime effectively eliminating all the duplication.

Your example doesn't look that bad because you only have one method per mixin, and you're only mixing them into one class. If you add more methods and start applying them to more classes, all of the forwarding methods will multiply in number and turn into a maintenance nightmare. For example, if you add a method to your TimeStamped class, you'll have to update every class you've mixed it into. With Dynaop, you don't have to do this.

John Lindsey

Posts: 1
Nickname: nisroc
Registered: Oct, 2005

Re: Mixins2 Posted: Oct 20, 2005 10:27 PM
Reply to this message Reply
Hmm, I don't like it. It goes against the GrainOfTheLanguage.

http://www.c2.com/cgi/wiki?GrainOfTheLanguage

Unfortunately, we can't use all of our favorite styles, techniques, and tricks in every language. To me, this example smells like a case where, even though we want Mixins, it would be better to just shrug it off and continue to write idiomatic java that everyone understands.

Vincent Oostindiƫ

Posts: 5
Nickname: voostind
Registered: Dec, 2003

Re: Mixins2 Posted: Oct 20, 2005 10:58 PM
Reply to this message Reply
I think the true power of mixins lies in the mixin reusing one or more methods in the 'main' class.

A great example of this is the Comparable mixin in Ruby. All you do is define the <=> operator in your own class, returning -1, 0 or 1. By mixing in Comparable in your class, you get all other comparison operators (<, <=, ==, !=, >, >=) for free! These operators use the <=> operator in their implementation.

I don't see how you would be able to do this in Java. How would the mixin be able to call a method on the original class? (And the answer should of course not be: 'with a lot of code' ;-)

But then again, I haven't looked at dynaop yet...

Michael Barker

Posts: 5
Nickname: mikeb01
Registered: Oct, 2005

Re: Mixins2 Posted: Oct 21, 2005 12:52 AM
Reply to this message Reply
Here is one way using dynamic proxies. You do have to cast from the proxy object to the interface (could this be overcome with some smart use of generics? I don't know generics well enough) so it is not 100% type safe, but most AOP mixins require this also. To keep the code short I have used duck-typing to get the delegate objects and it only works with methods that take 0 arguments.

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Date;
import java.util.HashMap;
 
interface TimeStamped {
  long getStamp();
}
 
class TimeStampedImp implements TimeStamped {
  private final long timeStamp;
  public TimeStampedImp() {
    timeStamp = new Date().getTime();
  }
  public long getStamp() { return timeStamp; }
}
 
interface SerialNumbered {
  long getSerialNumber();
}
 
class SerialNumberedImp implements SerialNumbered {
  private static long counter = 1;
  private final long serialNumber = counter++;
  public long getSerialNumber() { return serialNumber; }
}
 
class Basic {
  private String value;
  public void set(String val) { value = val; }
  public String get() { return value; }
}
 
class Mixin extends Basic {
  private TimeStamped timeStamp = new TimeStampedImp();
  private SerialNumbered serialNumber =
    new SerialNumberedImp();
  public TimeStamped getTimeStamped() { return timeStamp; }
  public SerialNumbered getSerialNumbered() {
    return serialNumber;
  }
}
 
 
class MixinProxy implements InvocationHandler {
 
    HashMap<String, Method> delegates = new HashMap<String, Method>();
    Object target;
    
    public MixinProxy(Object o, Class[] interfaces) throws SecurityException, NoSuchMethodException {
        
        this.target = o;
        for (Class clazz : interfaces) {
            Method delegateMethod = o.getClass().getMethod("get" + clazz.getSimpleName(), new Class[0]);
            for (Method m : clazz.getMethods()) {
                String methodName = m.getName();
                delegates.put(methodName, delegateMethod);
            }
        }
        
    }
    
    public Object invoke(Object proxy, Method m, Object[] args) throws Throwable {
        
        Method delegateMethod = delegates.get(m.getName());
        Object delegate = delegateMethod.invoke(target);
        return m.invoke(delegate);
    }
    
    public static Object newInstance(Object target, Class[] interfaces) throws IllegalArgumentException, SecurityException, NoSuchMethodException {
        ClassLoader cl = target.getClass().getClassLoader();
        return Proxy.newProxyInstance(cl, interfaces, new MixinProxy(target, interfaces));
    }
 
}
 
public class Test {
 
    public static void main(String[] args) throws IllegalArgumentException, SecurityException, NoSuchMethodException {
        Mixin m = new Mixin();
        Class[] interfaces = { TimeStamped.class, SerialNumbered.class };
        Object o = MixinProxy.newInstance(m, interfaces);
        TimeStamped t = (TimeStamped) o;
        SerialNumbered s = (SerialNumbered) o;
        System.out.println(t.getStamp());
        System.out.println(s.getSerialNumber());
        
    }
}

Bruce Eckel

Posts: 875
Nickname: beckel
Registered: Jun, 2003

Re: Mixins2 Posted: Oct 21, 2005 3:16 AM
Reply to this message Reply
Wow. I was hoping for some way to use generics here, which may simply be impossible because of the limitations of Java generics for all I can tell at this point. But if you look at the elegance and simplicity in C++ of both the creation of the classes to be mixed in, and the mixin process itself, which is:

TimeStamped<SerialNumbered<string> > mixin1, mixin2;

versus the solutions that have been proposed: A) Write lots of code (my example) B) Write at least as much code (the dynamic proxy) which is more complicated in hopes of reducing the code as things get bigger C) Create a new language (Dynaop) which also has quite a bit of complexity, I'm tempted to agree with John Lindsey and say that "Java was simply not intended to do such a thing" and attribute this to yet another failure of imagination.

The only solution that has the same elegance as the C++ approach is the MixGen compiler described in the paper "A First-Class Approach to Genericity," but that is another new language, but designed solely around the idea of creating mixins (and the necessity of creating a non-erasure-based generic system to support it).

What's interesting is that if you step back a bit, you can see that there's a group of often distinctly different mechanisms attempting to solve the same problem; in this case, the desire to implement what I'll call "functionality injection" -- to paste on new methods to existing code in some way other than actually sticking the methods in by hand in each class. Of course, we know from the example of design patterns that sometimes subtle differences in intent can produce radically different results, and yet it seems to me there is a fundamental consistency with mixins, aspects, the visitor pattern, multimethods (which are in the Nice language and will be in C# 3.0) and certainly several other techniques that don't currently come to mind. They all try to produce a more formal way to paste new methods into existing classes.

But so far, no one has jumped up waving their hand with a clever and clean way to do mixins in J2SE5; quite the contrary, the paper "A First-Class Approach to Genericity," goes to a lot of trouble to show that erasure-based generics cannot yield such a solution. It would seem that all this can only be used to motivate consideration of alternative approaches like AOP or the MixGen compiler, or possibly Nice (which does not use erasure in its generics, which suggests that it might handle mixins elegantly).

jeff robertson

Posts: 1
Nickname: jrobert1
Registered: Oct, 2005

Re: Mixins2 Posted: Oct 21, 2005 5:29 AM
Reply to this message Reply
Bruce, sicne you don't "go for" either dynaop or for the other posted suggestion to use proxies, you probably won't like mine either.. it's basically more in the same vein. But here goes anyway.

I have written a library class called "DyanmicDelegator" that seems like it would solve at least some of your mixin needs. Here is an example that mixes the Map and Runnable interfaces.


public class MapWrapper extends DynamicDelegator implements Runnable {

public MapWrapper(Object o) {
super(o);
}

public Object put(Object key, Object value) {
System.out.println("Put called!");
return ((Map)wrapped).put(key,value);
}

public void run() {
System.out.println("Whoohoo!");
}
}


The resulting object can be cast to either a map or a runnable.


Map m = (Map) new MapWrapper(new HashMap()).getProxy();
m.put("foo","bar");
System.out.println( m.get("foo"));
((Runnable)m).run();


Notice that you don't need to declare that it implements Map, only Runnable. The ability to be cast as a Map is automatically picked up as a result of being wrapped around the HashMap. The real reason I originally wrote this was to intercept methods, like the above does with Map.put().. the "mixin" behavior as shown with Runnable is sort of a side bonus.

This blog post contains the entire source for DynamicDelegator.

http://www.jroller.com/page/jeffrobertson?entry=dynamicdelegator_a_cleaner_kind_of

Bob Lee

Posts: 4
Nickname: crazybob
Registered: Oct, 2005

Re: Mixins2 Posted: Oct 21, 2005 11:15 AM
Reply to this message Reply
> C) Create a new
> language (Dynaop) which also has quite a bit of
> complexity, I'm tempted to agree with John Lindsey and say
> that "Java was simply not intended to do such a thing" and
> attribute this to yet another failure of imagination.

Dynaop doesn't create a new language. It does have a configuration file with two elements (interceptors and mixins). Think of Dynaop as a support framework for the decorator pattern.

You could also use annotations and apt. You could annotate your class with a number of mixins: @Mixin(TimeStampedImp.class). Next use apt to generate your "Mixins" class. This is basically the exact same thing that Dynaop does except Dynaop does it at runtime.

Todd Blanchard

Posts: 316
Nickname: tblanchard
Registered: May, 2003

Re: Mixins2 Posted: Oct 21, 2005 10:28 PM
Reply to this message Reply
Yes, but interfaces usually have default implementations that you have to reproduce over and over again which leads to pointless monkey coding.

The bottom line is Java is a really crummy OO language that doesn't have decent support for code factoring. Which is a lot of why I don't use it anymore.

As for Bloch, he wrote the collections library and nowhere else is the poor choice of abstractions (empty interfaces and single inheritance only) more obvious than in the tortuous collections hierarchy.

Next trend please.

Michael Barker

Posts: 5
Nickname: mikeb01
Registered: Oct, 2005

Re: Mixins2 Posted: Oct 24, 2005 9:10 AM
Reply to this message Reply
Here is a second attempt again using dynamic proxies. This is a bit nicer than the previous version in that none of the mixed in classes required modification, but all functionality of the mixin must be defined in interfaces.

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
 
interface TimeStamped {
    long getStamp();
}
 
class TimeStampedImp implements TimeStamped {
    private final long timeStamp;
 
    public TimeStampedImp() {
        timeStamp = new Date().getTime();
    }
 
    public long getStamp() {
        return timeStamp;
    }
}
 
interface SerialNumbered {
    long getSerialNumber();
}
 
class SerialNumberedImp implements SerialNumbered {
    private static long counter = 1;
 
    private final long serialNumber = counter++;
 
    public long getSerialNumber() {
        return serialNumber;
    }
}
 
interface Basic {
    public void set(String val);
 
    public String get();
}
 
class BasicImp implements Basic {
    private String value;
 
    public void set(String val) {
        value = val;
    }
 
    public String get() {
        return value;
    }
}
 
class MixinProxy implements InvocationHandler {
 
    Map<String,Object> delegatesByMethod;
    
    public MixinProxy(Pair... pairs) {
        delegatesByMethod = new HashMap<String,Object>();
        
        for (Pair pair : pairs) {
            for (Method method : pair.cls.getMethods()) {
                String methodName = method.getName();
                // The first interface in the map is the one that implements
                // The method.
                if (!delegatesByMethod.containsKey(methodName)) {
                    delegatesByMethod.put(methodName, pair.obj);
                }
            }
        }
    }
    
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        String methodName = method.getName();
        Object delegate = delegatesByMethod.get(methodName);
        return method.invoke(delegate, args);
    }
 
    public static Object newInstance(Pair... pairs) {
        
        Class[] interfaces = new Class[pairs.length];
        
        for (int i = 0; i < pairs.length; i++) {
            interfaces[i] = pairs[i].cls;
        }
        
        ClassLoader cl = pairs[0].obj.getClass().getClassLoader();
        return Proxy.newProxyInstance(cl, interfaces, new MixinProxy(pairs));
    }
}
 
class Pair {
    Object obj;
    Class cls;
    public Pair(Object obj, Class cls) {
        this.obj = obj;
        this.cls = cls;
    }
}
 
 
public class MixinTest {
 
    public static void main(String[] args) {
        
        Object mixin = MixinProxy.newInstance(
                new Pair(new BasicImp(), Basic.class),
                new Pair(new TimeStampedImp(), TimeStamped.class),
                new Pair(new SerialNumberedImp(), SerialNumbered.class)
        );
        
        Basic b = (Basic) mixin;
        TimeStamped t = (TimeStamped) mixin;
        SerialNumbered s = (SerialNumbered) mixin;
        b.set("Hello");
        System.out.println(b.get());
        System.out.println(t.getStamp());
        System.out.println(s.getSerialNumber());
    }
}

Bruce Eckel

Posts: 875
Nickname: beckel
Registered: Jun, 2003

Re: Mixins2 Posted: Oct 24, 2005 9:41 AM
Reply to this message Reply
Thanks, Michael. This looks straightforward enough that it might something I might be able to massage (for example, I already have a Tuple (generic) class in the book, so I wouldn't need to have a Pair). And it does show a nice contrast between an actual mixin and the Decorator pattern -- the actual mixin is the type of all the types that were mixed, whereas the decorator is the type of the last decorator that wrapped your object.

John Connors

Posts: 1
Nickname: johnfred
Registered: Oct, 2005

Re: Mixins2 Posted: Oct 26, 2005 7:56 AM
Reply to this message Reply
May I suggest you read this "Jam A Smooth Extension of Java with Mixins (1999)"?

http://citeseer.ist.psu.edu/246393.html

John Fred

Bruce Eckel

Posts: 875
Nickname: beckel
Registered: Jun, 2003

Re: Mixins2 Posted: Oct 26, 2005 11:43 AM
Reply to this message Reply
> May I suggest you read this "Jam A Smooth Extension of
> Java with Mixins (1999)"?
>
> http://citeseer.ist.psu.edu/246393.html
>
> John Fred

Just began reading this, but the first part of it seems to have a better description of mixins than I've seen elsewhere.

But it's also interesting to note how many people have tried to solve this problem. It's clearly on a lot of minds. Creating and implementing a language like Jam is a lot of effort, so clearly they thought mixins where important.

Bruce Eckel

Posts: 875
Nickname: beckel
Registered: Jun, 2003

Re: Mixins2 Posted: Oct 26, 2005 3:50 PM
Reply to this message Reply
> Here is a second attempt again using dynamic proxies.

Michael, please email me. I'd like to footnote your contribution to this example.

James Sasitorn

Posts: 1
Nickname: camus
Registered: Nov, 2005

Re: Mixins2 Posted: Nov 16, 2005 12:50 PM
Reply to this message Reply
My work on the NextGen Generic Java Compiler (http://japan.cs.rice.edu/nextgen/) provides alpha-level support for mixins. NextGen is an extended version of the Java 5.0 javac compiler (available for use under the SCSL). NextGen supports first-class generic types (including mixins) by using light-weight template classes.

Currently NextGen is able to handle simple Mixin instantations (with hygiene). For example:


class TimeStamped<T> extends T {
public long time;
public TimeStamped() {
time = new java.util.Date().getTime() +(long)(Math.random()*1000);
}
public long getTime() { return time; }
public long getTime2() { return getTime(); }
}


class Box { public Box() { } }

class Mixin1Test extends junit.framework.TestCase {
public Mixin1Test(String name) { super(name); }
Object o;
public void setUp() {
o = new TimeStamped<Box>();
}

public void test_tsbox() {
assertTrue (new TimeStamped<Box>() instanceof TimeStamped<Box>);
}

public static void main(String[] args) {
TimeStamped<Box> my = new TimeStamped<Box>();
TimeStamped<TimeStamped<Box>> my2 = new TimeStamped<TimeStamped<Box>>();
System.out.println("A " + my.getTime());
System.out.println("B " + my2.getTime());
System.out.println("C " + ((TimeStamped<Box>)my2).getTime());
}
}


I have been looking for larger examples to test the robustness of the compiler . Any ideas will be appreciated.

James

Flat View: This topic has 16 replies on 2 pages [ 1  2 | » ]
Topic: How is Moore's Law really changing computing? Previous Topic   Next Topic Topic: Introducing Neon

Sponsored Links



Google
  Web Artima.com   

Copyright © 1996-2019 Artima, Inc. All Rights Reserved. - Privacy Policy - Terms of Use