Skip to main content

Strings in switch and closures

Posted by forax on November 15, 2009 at 7:28 AM PST

As you perhaps already know, jdk7 milestone 5 (jdk7b76) is
out.


This new milestone contains a lot of bug fixes and improvements.
This blog entry is about two of them:

  • Strings in switch
  • Method handles (yes again)

Switch on String

This one of the small proposal changes (from
Coins's project) to Java language in order to
allow to switch on String values.


Let's take an example. Suppose you want a program with these outputs:

  $ java StringSwitch hello
  hello
  $ java StringSwitch -verbose hello
  hello switch string

Here is the corresponding Java code:

public class StringSwitch {
  public static void main(String[] args) {
    boolean verbose = false;
    for(String arg:args) {
      switch(arg) {
      case "hello":
        if (verbose)
          System.out.println("hello switch string");
        else
          System.out.println("hello");
        break;
      case "-verbose":
        System.out.println("verbose mode activated");
        verbose = true;
        break;
      }
    }
  }
}

And without jdk7

Here is another program with the same semantics but
without the switch on strings. It uses a HashMap
to avoid a cascade of if/else which could be very slow (*).
So for each string, I associate a runnable, which is the code to run
if the string match. Also note that because I define the code to run
using an anonymous class, the local variable verbose has to be a captured
and because this variable is modified, I use the usual workaround and
declare it has an array of one element.

* Ok, in this example, because there is only two strings, this is not that slow.

public class StringHash {
  public static void main(String[] args) {
    final boolean[] verbose = new boolean[]{false};
    HashMap<String,Runnable> map =
      new HashMap<String, Runnable>();
    map.put("hello", new Runnable() {
      @Override
      public void run() {
        if (verbose[0])
          System.out.println("hello switch string");
        else
          System.out.println("hello");
      }
    });
    map.put("-verbose", new Runnable() {
      @Override
      public void run() {
        System.out.println("verbose mode activated");
        verbose[0] = true;
      }
    });
   
    for(String arg:args) {
      Runnable runnable = map.get(arg);
      if (runnable!=null)
        runnable.run();
    }
  }
}

With closure

In fact, the program above define two closures.
By definition a closure is a function
that is able to capture variables.
Java 7 will not include a closure syntax but jdk7 VM already includes runtime support for closures.
The current status of closure in Java can be seem as the opposite of the status of generics.
Generics are a compiler artifact not understood by the VM (there is no reification).
Closure are understood by the VM (using the package
java.dyn)
but there are not known by the Java type system.

The program below shows how to use method handle to solve our running problem.
findStatic
creates a method handle from an existing method (*) found
by providing its declaring class, its name and its signature; a
MethodType.
insertArgument
(also named currying) is a way to bound a value to a method handle
to create a closure. Like with anonymous class syntax, the bounded value is a value and not a variable.
Calling a method handle is done by using the method invoke which is polymorphic;
that's why you have to specify the return type;
the VM will verify that the declared types and the
MethodType
of the method handle are identical.

* In a close future, this will be done in one instruction by the VM, but I don't know
if this instruction will surface in Java.

public class StringMH {
  private static void hello(boolean[] verbose) {
    if (verbose[0])
      System.out.println("hello switch string");
    else
      System.out.println("hello");
  }
  private static void verbose(boolean[] verbose) {
    System.out.println("verbose mode activated");
    verbose[0] = true;
  }
 
  public static void main(String[] args) {
    boolean[] verbose = new boolean[]{false};
    HashMap<String,MethodHandle> map =
      new HashMap<String,MethodHandle>();
   
    Lookup lookup = MethodHandles.lookup();
    MethodType methodType = MethodType.make(void.class, boolean[].class);
    MethodHandle helloMH = lookup.findStatic(StringMH.class, "hello", methodType);
    MethodHandle verboseMH = lookup.findStatic(StringMH.class, "verbose", methodType);
   
    map.put("hello", MethodHandles.insertArgument(helloMH, 0, verbose));
    map.put("-verbose", MethodHandles.insertArgument(verboseMH, 0, verbose));
   
    for(String arg:args) {
      MethodHandle mh = map.get(arg);
      if (mh!=null)
        mh.<void>invoke();
    }
  }
}

Because method handle code in the VM is not production ready you have to add some flags
to be able to run this program. The VM will also output a warning saying that the JDK and the VM
code are not aligned fortunately this program doesn't use that part of the code.

 java -XX:+UnlockExperimentalVMOptions -XX:+EnableInvokeDynamic StringMH -verbose hello

Enough fun for today, see you soon.


Cheers,


Rémi

Related Topics >>

Comments

Overly complicated examples

I personally think the "Without JDK 7" example has been overly complicated for such a small problem as comparing strings, just to sell the idea of closures some how. Using a Runnable just to run a match?

Re: Overly complicated example

I agree with you.

Depending on the OS you have up to one thousand arguments so no need to use a hash map here.

If you want some real use cases: the scanner used by javac to match keywords doesn't use a cascade of if/else,
any SAX parser handlers will not use (reality: should not use) a cascade of if/else.

Now let me dream, just a bit. Suppose that Josh Bloch comes with an implementation of map literal
(which one of the small language changes of Coin project) and
suppose that Java supports closures, your overly complicated code can be written like that::

    shared boolean verbose = false;
    Map&lt;String, fun void()> map = {
     "hello": lambda() {
        System.out.println("verbose mode activated");
        verbose = true;
     },
     "-verbose": lambda() {
       if (verbose)
          System.out.println("hello switch string");
        else
          System.out.println("hello");
     }
   };
   for(String arg:args) {
     fun void() option= map.get(arg);
      if (option!=null)
        option.invoke();
   }

Cheers,

Rémi

Remy, What about: boolean

Remy, What about: boolean verbose = false; for(String arg:args) { switch(arg.hashCode()) { case HELLO_HASHCODE: if (verbose) System.out.println("hello switch string"); else System.out.println("hello"); break; case VERBOSE_HASHCODE: System.out.println("verbose mode activated"); verbose = true; break; } } } Best, Mike

Using hashcode

This is what jdk7's javac generates when you do a switch on String.
BTW, do you see that you can't declare VERBOSE_HASHCODE like that:
private static final int VERBOSE_HASHCODE = "-verbose".hashCode();

Rémi

Yes, I usually compute the

Yes, I usually compute the value in an interactive interpreter of mine, then put the code

private static final int VERBOSE_HASHCODE = 1468161205; // "-verbose".hashCode()
I guess the comment plus the variable name makes it readable :)

Yes, I usually compute the

Yes, I usually compute the value in an interactive interpreter of mine, then put the code

Does anyone else but me get the feeling that this is another example for letting tools do what the language itself should do?
So an "interactive interpreter" is now part of the IDE, necessary to write meaningful Java code?

To me it is like adding values with a desk calculator to put the result into Excel cells ...

Closures synyax

I understand that support of closures syntax is not in the scope defined for jdk 7. But what I find very bad is that work on closures syntax for java (see http://javac.info/ and closures-dev mailing list) was stopped long time ago and nothing happens there. I believe that now having jvm support for closures it a right time to resume work closures syntax support for java programming language. I understand that it is not you who makes such decisions but just wondered if you have header anything about plans for closures syntax for java programming language. Thanks, Serhiy

The community has shown

The community has shown clearly that closures (or function pointers, which is what they really are) are not a desired addition to the language. It's only a small group of people with no feeling with what's happening in the real world who are trying to push them. Having support for them at bytecode format allows their use in other languages that are available on the JVM, forcing them down the throat of Java developers is not at all the same thing and certainly is not desirable.

The community has shown

The community has shown clearly that closures (or function pointers, which is what they really are) are not a desired addition to the language.

It's sad to recognise after such a long time I'm now working with Java (since 1.1.4), that I am obviously not considered as part of "the community".

It's only a small group of people with no feeling with what's happening in the real world who are trying to push them.

It's hard to suddenly wake up and see that apparently all my professional enterprise programming of the last years was only a bad dream and not the "real world".

But on the other hand: perhaps the definition of "the community" and "the real world" has been made by someone sitting in a cave and looking at the shadows on the wall...

forcing them down the throat of Java developers is not at all the same thing and certainly is not desirable.

Like it has happened with Generics?
It is really a pity that people working in a business called "development" are so locked against modernisations that they consider them as "forcing something down their throats".

Re: the community has shown

Hi Jeroen,

I think you're wrong.

Python has closure, Ruby has closure, Javascript has closure, Groovy has closure,
Objective-C has closure, C# has closure, C++ will have closure
(just to cite some mainstream non-functional languages) so closures seem to answer to some common problems.

Even Java has closure, anonymous classes are a kind of closure but the syntax requires to declare
an interface which is stupid.

I want to be able do concurrency, with co-coroutine, etc. in Java
(see http://www.ibm.com/developerworks/java/library/j-jtp03048.html)
that why I need closure.

I don't want Neal's full featured closure, just simple closure.

Saying that I havent the feeling to be deconnected from the Java community.

cheer,

Rémi

goto also

COBOL has goto, FORTRAN has goto, BASIC has goto, C has goto, C++ has goto, C# has goto, pascal has goto, perl has goto. So goto seems to answer some common problems. Even Java has goto, labelled breaks are a kind of goto. The issue is not whether or not the feature *can* be useful, it's whether its typical real-world usage will cause code to be more or less maintainable.

real world?

I'm not sure why you're so categorically against lambdas in Java. They are very useful in simplifying code and reducing code size. Code is more maintainable when it's easier to read, code is easier to read if it has less boilerplate obscuring the actual functionality. Lambdas help quite a bit in this regard.

real world.

I'm not sure why you'd think I'm categorically against lambdas in Java. All I did was point out the fallacy of the "other languages have it so it must be good" argument.

Real World

Sorry Andy,

I was thinking that the last sentence of your previous post was related to lambdas.

Rémi

Re: goto also

Andy,

You're right goto is useful :)


So the question seems to be "Is the following code maintainable or not":

List&lt;String&gt; list = ...
Collections.forEach(list, <b>lambda</b>(String value) {
  System.out.println(value);
});

with Collections.forEach declared like this:
void forEach(Collection&lt;? extends E&gt; c, <b>fun</b> void(E) closure) {
  for(E element: c) {
    closure.invoke(element);
  }
}

In my opinion, it's not far from actual Java.
I don't see why you can claim it's less maintainable.

cheers,

Rémi

Aha! Currying! Now, does

Aha! Currying! Now, does HotSpot yet attempt any sort of optimisation on the curried resulting function, eg constant propagation? Rgds Damon

Hot curry

Hi Damon,

As far as I know, Hotspot does two kinds of optimisation on MethodHandles.insertArguments.

  1. If the bind value is an object and the method handle a method
    (created by example using findVirtual or findInterface)
    the hotspot will issue the same code as a method call code.
    I think jdk7 m5 already do this optimisation.

  2. The other optimisation is to tell the VM that all method handles adapters
    (the ones you can find in java.dyn.MethodHandles) must be inlined.
    After being inlined, constant propagation will be done like all
    already existing optimisations done by Hotspot.
    This optimisation is not currently done by jdk7 m5 but the
    patch already exists in mlvm repository and at some point in the future
    will be pushed in jdk repository.

regards,

Rémi