Rick

Rick
Rick

Wednesday, June 6, 2012

Easy Java, scratching an itch

Easy Java

I have had this itch for a while. I love to work in languages like Python and Groovy, but seem to do a lot of Java development. It pays the bills as it were. To me, it would be nice to have some of language features from Python and Groovy in Java.

Literals for Maps, Lists, some functional programming, some dynamic method invocation here and there.

I starter scratching my itch a bit when I worked on Crank a few years ago. I created a DAO framework with a DSL like criteria in Java. Mostly using vargs and such.

My main focus is going to be on Python, then maybe take a look at Groovy and Ruby and see what can be done. This will all be Java language constructs. Nothing really fancy, generics, reflection, static imports, vargs, etc.

The idea is to try to add things like map literals, functional programming, realizing that this is not really possible 100% of the way with Java.

My goal is to under engineering things as much as possilbe and not try to over design things. Easy for the end user developer to use and grok.

Using the Python docs as an inspirataion, there is already some code that is fairly close to its Python example.

Easy Java, Python functions. (Java versus Python)

Java is strongly typed so I have to declare the variable tel.

The code was insprired by the python dictionary docs.

Easy Java versus Python Dictionaries

import static com.easyjava.EasyJava.; import java.util.; public class Example {

public static void main(String [] args) {
    Map<String, Integer> tel;

Python dictionary literal

    tel = {'jack': 4098, 'sape': 4139}

Easy Java Map function

    tel = mp("jack", 4098, "sape", 4139);

They are actually pretty close in size. The function mp is from com.easyjava.EasyJava (not the final name of the project).

Working dictionary in Python

    tel['guido'] = 4127
    print (tel)
    print (tel['jack'])
    del tel['sape']
    tel['irv'] = 4127
    print(tel)
    print(tel.keys())

Working map in Java

    tel.put("guido", 4127);     
    print(tel);
    print(tel.get("jack"));     
    tel.remove("sape");
    tel.put("irv", 4127);
    print(tel);
    print (tel.keySet());

One area where they differ is the in operator.

            print( 'guido' in tel ) #Python
    print( tel.containsKey("guido") ); #Java

I could create a funciton called in that takes two args to make it closer. But, this might be a bit silly. It would look something like:

            print('guido' in tel) #Python
    print (in("guido", tel); #Java

I don't plan on writing my own collection API.

Output for working with dictionary in Python

            {'sape': 4139, 'guido': 4127, 'jack': 4098}
    4098
            {'guido': 4127, 'irv': 4127, 'jack': 4098}
    ['guido', 'irv', 'jack']
    True

Output for working with map in Java

    {sape=4139, jack=4098, guido=4127} 
    4098
    {irv=4127, jack=4098, guido=4127} 
    [irv, jack, guido] 
    true

Python dict function

    tel = dict([('sape', 4139), ('guido', 4127), ('jack', 4098)])
    print (tel)

Easy Java dict function

    tel = dict(kv("sape", 4139), kv("guido", 4127), kv("jack", 4098));
    print(tel);

The size is quite close. The dict and kv are static methods.

Output Python then Java

 {'sape': 4139, 'jack': 4098, 'guido': 4127}    
 {sape=4139, jack=4098, guido=4127}

In the next blog post, I will cover list comprehension and functional programming.

But just in case, you can't wait until then.

Here is all of my code with no explanation (implemenation then example code):

EasyJava early prototype

package com.easyjava;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.HashMap;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

public class EasyJava {
    private static Logger log = Logger.getLogger(EasyJava.class.getName());

    public static void print (Object... items) {
        StringBuilder builder = new StringBuilder(256);
        for (Object item : items) {
            builder.append(item);
            builder.append(' ');
        }
        System.out.println(builder);
    }

    @SuppressWarnings("serial")
    public static class AssertionException extends RuntimeException {

        public AssertionException() {
            super();
        }

        public AssertionException(String message, Throwable cause) {
            super(message, cause);
        }

        public AssertionException(String message) {
            super(message);
        }

        public AssertionException(Throwable cause) {
            super(cause);
        }       
    }

    @SuppressWarnings("serial")
    public static class ReflectionException extends RuntimeException {

        public ReflectionException() {
            super();
        }

        public ReflectionException(String message, Throwable cause) {
            super(message, cause);
        }

        public ReflectionException(String message) {
            super(message);
        }

        public ReflectionException(Throwable cause) {
            super(cause);
        }       
    }

    public static void die(boolean condition, String message) {
        if (condition) {
            throw new AssertionException(message);
        }
    }
    public static void die(String message) {
        throw new AssertionException(message);
    }

    public static void die(String message, Object... args) {
        throw new AssertionException(String.format(message, args));
    }

    public static interface Entry<K,V> {
        K key();
        V value();
    }

    private static class EntryImpl<K, V> implements Entry<K, V>{
        EntryImpl(K k, V v) {this.k=k; this.v=v;}
        K k;
        V v;
        @Override
        public K key() {
            return k;
        }
        @Override
        public V value() {
            return v;
        }
    }

    public static interface Function {
        Method method();
        Object [] args();
        Object that();
        Object execute(Object... params);
    }

    private static class FunctionImpl implements Function {
        FunctionImpl(Method method, Object [] args, Object that) {this.method=method; this.args=args; this.that = that;}
        Method method;
        Object [] args;
        Object that;


        @Override
        public Method method() {
            return method;
        }

        @Override
        public Object[] args() {
            return args;
        }

        @Override
        public Object that() {
            return that;
        }

        public Object execute(Object... params) {
            try {
                List<Object> paramsList = list(params);
                if (args!=null && args.length > 0) {
                    paramsList.addAll(list(args));
                }
                return method.invoke(that, paramsList.toArray());
            } catch (Exception ex) {

            }
            return null;
        }

    }

    public static <K, V> Entry<K, V> kv (final K k, final V v) {
        return new EntryImpl<K,V>(k, v);
    }

    public static <K, V> Entry <K, V> entry (final K k, final V v) {
        return new EntryImpl<K,V>(k,v);
    }

    public static <V> List<V> list(V... array) {
        return Arrays.asList(array);
    }

    public static <V> Set<V> set(V... array) {
        return new HashSet<V>(list(array));
    }

    public static Function f(Object that, Object... args) {
        try {
            return fn(that, "f", args);
        } catch (Exception e) {
            throw new ReflectionException(e);
        }
    }

    public static Function fn(Object that, Object name, Object... args) {
        try {
            Method[] methods = clazz(that).getDeclaredMethods();
            for (Method m : methods) {
                if (args.length <= m.getParameterTypes().length) {
                    if (m.getName().equals(name.toString())) {
                        m.setAccessible(true);
                        if (Modifier.isStatic(m.getModifiers())) {
                            return new FunctionImpl(m, args, null);
                        } else {
                            return new FunctionImpl(m, args, that);
                        }
                    }
                }
            }
        } catch (Exception ex) {
            log.log(Level.SEVERE, String.format("Unable to find function that=%s, name=%s, args=%s", that, name, list(args)), ex);
        }
        die("Unable to find function that=%s, name=%s, args=%s", that, name, list(args));
        return null;
    }

    private static Class<?> clazz(Object that) {
        if (that instanceof Class) {
            return (Class<?>) that;
        } else {
            return that.getClass();
        }
    }
    public static Map<Object, Object> dict(Function f, Collection<?> c) {
        HashMap<Object, Object> map = new HashMap<Object, Object>(5);
        for (Object o : c) {
            map.put(o, f.execute(o));
        }
        return map;
    }
    public static <K, V> Map<K, V> mp(K k0, V v0) {
        HashMap<K, V> map = new HashMap<K, V>(10);
        map.put(k0, v0);
        return map;
    }
    public static <K, V> Map<K, V> mp(K k0, V v0, K k1, V v1) {
        HashMap<K, V> map = new HashMap<K, V>(10);
        map.put(k0, v0);
        map.put(k1, v1);
        return map;
    }

    public static <K, V> Map<K, V> mp(K k0, V v0, K k1, V v1, K k2, V v2) {
        HashMap<K, V> map = new HashMap<K, V>(10);
        map.put(k0, v0);
        map.put(k1, v1);
        map.put(k2, v2);
        return map;
    }

    public static <K, V> Map<K, V> mp(K k0, V v0, K k1, V v1, K k2, V v2, K k3, V v3) {
        HashMap<K, V> map = new HashMap<K, V>(10);
        map.put(k0, v0);
        map.put(k1, v1);
        map.put(k2, v2);
        map.put(k3, v3);
        return map;
    }

    public static <K, V> Map<K, V> mp(K k0, V v0, K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4) {
        HashMap<K, V> map = new HashMap<K, V>(10);
        map.put(k0, v0);
        map.put(k1, v1);
        map.put(k2, v2);
        map.put(k3, v3);
        map.put(k4, v4);
        return map;
    }

    public static <K, V> Map<K, V> mp(K k0, V v0, K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5) {
        HashMap<K, V> map = new HashMap<K, V>(10);
        map.put(k0, v0);
        map.put(k1, v1);
        map.put(k2, v2);
        map.put(k3, v3);
        map.put(k4, v4);
        map.put(k5, v5);
        return map;
    }

    public static <K, V> Map<K, V> mp(K k0, V v0, K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5, Entry<K,V>...entries) {
        HashMap<K, V> map = new HashMap<K, V>(10 + entries.length);
        map.put(k0, v0);
        map.put(k1, v1);
        map.put(k2, v2);
        map.put(k3, v3);
        map.put(k4, v4);
        map.put(k5, v5);
        for (Entry<K,V> entry: entries) {
            map.put(entry.key(), entry.value());
        }
        return map;
    }

    public static <K, V> Map<K, V> dict(Entry<K,V>...entries) {
        HashMap<K, V> map = new HashMap<K, V>(5);
        for (Entry<K,V> entry: entries) {
            map.put(entry.key(), entry.value());
        }
        return map;
    }

    public static void enumerate(Function f, Collection<?> c) {
        int index = 0;
        for (Object o : c) {
            f.execute(index, o);
            index++;
        }
    }

    public static Collection<?> filter(Function f, List<?> l) {
        ListIterator<?> listIterator = l.listIterator();
        while (listIterator.hasNext()) {
            Boolean b = (Boolean) f.execute(listIterator.next());
            if (!b) {
                listIterator.remove();
            }
        }
        return l;
    }

    public static Collection<?> filter(Function f, Collection<?> c) {
        ArrayList<Object> removeList = new ArrayList<Object>(c.size());
        for (Object o : c) {
            Boolean b = (Boolean) f.execute(0);
            if (!b) {
                removeList.add(o);
            }           
        }
        for (Object o: removeList) {
            c.remove(o);
        }
        return c;
    }

    public static List<Integer> range(int stop) {
        return range(0, stop);
    }

    public static List<Integer> range(int start, int stop) {
        ArrayList<Integer> range = new ArrayList<Integer>(stop-start);
        for (int index=start; index<stop; index++) {
            range.add(index);
        }
        return range;
    }

    public static List<Integer> range(int start, int stop, int inc) {
        ArrayList<Integer> range = new ArrayList<Integer>(stop-start);
        for (int index=start; index<stop; index+=inc) {
            range.add(index);
        }
        return range;
    }

    public static List<?> map(Function f, Collection<?> c) {
        ArrayList<Object> mapList = new ArrayList<Object>(c.size());
        for (Object o : c) {
            mapList.add(f.execute(o));
        }
        return mapList;
    }

    public static Object reduce(Function f, Collection<?> c) {
        Object accumulo = null;
        int index=0;
        for (Object o : c) {
            if (index==0) {
                accumulo = o;
            } else {
                accumulo = f.execute(accumulo, o);
            }
            index++;
        }
        return accumulo;
    }

}

Example using functional programming etc. from EasyJava

package com.examples;

import static com.easyjava.EasyJava.*;
import java.util.*;



public class Example {


    @SuppressWarnings({"unchecked", "unused"})
    public static void main(String [] args) {
        Map<String, Integer> tel;

        //tel = {'jack': 4098, 'sape': 4139}
        tel = mp("jack", 4098, "sape", 4139);

        //tel['guido'] = 4127
        tel.put("guido", 4127);

        //print (tel)
        print(tel);
        //Output {'sape': 4139, 'guido': 4127, 'jack': 4098}

        // print (tel['jack'])
        print(tel.get("jack"));
        //Output 4098

        // del tel['sape']
        tel.remove("sape");


        // tel['irv'] = 4127
        tel.put("irv", 4127);


//      print(tel)
        print(tel);
//      Output {'guido': 4127, 'irv': 4127, 'jack': 4098}

//      print(tel.keys())
        print (tel.keySet());
//      Output ['guido', 'irv', 'jack']


//      'guido' in tel
        print (tel.containsKey("guido"));
//      Output True


//      tel = dict([('sape', 4139), ('guido', 4127), ('jack', 4098)])
        tel = dict(kv("sape", 4139), kv("guido", 4127), kv("jack", 4098)); //get unchecked exception for Java 6 but not Java 7
//      print (tel)
        print(tel);
//      Output {'sape': 4139, 'jack': 4098, 'guido': 4127}


        //List Comprehension
         Class <?> c = Example.class;


//      print( dict([(x, x**2) for x in (2, 4, 6)]) )     # use a list comprehension
//      Output {2: 4, 4: 16, 6: 36}
        print (dict(fn(c, "pow2"), list(2, 4, 6)));

//      >>> for i, v in enumerate(['tic', 'tac', 'toe']):
//          ...     print i, v
//          ...
//          0 tic
//          1 tac
//          2 toe
        enumerate(fn(c, s.printItem), list("tic", "tac", "toe"));

        //You can also declare functions on the fly like so
        class Foo {
            void printItem(int i, String v) {
                print("foo", i , v);
            }           
        }
        enumerate(fn(new Foo(), s.printItem), list("tic", "tac", "toe"));

//      >>> def f(x): return x % 2 != 0 and x % 3 != 0
//              ...
//      >>> filter(f, range(2, 25))
//          [5, 7, 11, 13, 17, 19, 23]      

        class Filter {
            boolean f(int x) {
                return x % 2 != 0 && x % 3 != 0;
            }           
        }
        print (filter(f(new Filter()), range(2, 25)));

//      >>> def cube(x): return x*x*x
//              ...
//      >>> map(cube, range(1, 11))
//      [1, 8, 27, 64, 125, 216, 343, 512, 729, 1000]

        print (map(fn(c, s.cube), range(1,11)));

//      >>> def add(x,y): return x+y
//              ...
//      >>>      reduce(add, range(1, 11))
//      55

        print ( reduce(fn(c,s.add), range(1,11)) );
    }

    static int add(int x, int y) {
        return x + y;
    }

    static int cube(int i) {
        return i * i * i;
    }

    static int pow2(int i) {
        return i * i;
    }

    static void printItem(int i, String v) {
        print(i , v);
    }

    static enum s {printItem, cube, add};


}

1 comment:

  1. An interesting premise to be sure. Reminds me of the Joda project. Let's wrap Calendar because Calendar is too hard to use.

    I guess as an old java warhorse, I just don't think in terms of "this is hard". I just use calendar...(or...collections...)

    In the case of EasyJava - I take your point. Some things in Java feel unwieldy at first, things which other languages make feel effortless. Having done Python, Ruby, AS3, I have lived on both sides. What I love about Java as-it-is, is the way the strictness of the language enforces a common understanding. It can be easy for me to get lost in less strict languages. It can also be quicker for me to write the code.

    I see the balance you are trying to achieve here, and the bridge you are trying to build. Some cautioning words:

    * The library must be extremely lightweight
    * The library must be terminally backwards compatible
    * The library must have very well done javadoc for command completion
    * The library must demonstrate a real value add, beyond a 'feel good' for dynamic language preferrers, to be highly adoptable.


    Just my .05...

    P.S. I like how your headshot mostly shows your torso :D

    ReplyDelete

Kafka and Cassandra support, training for AWS EC2 Cassandra 3.0 Training