Gulf of execution

In his book The design of everyday things, Norman introduced the terms gulf of evaluation and gulf of execution. The latter is the difference between the intentions of the user and what the system allows them to do or how well the system supports those actions.

This describes perfectly my experience as I moved from Golang to Java.

I find myself asking questions like:

  • What is the least painful way of iterating over a map?
  • How do I make lazy functions?
  • How do I run something in a different thread?
  • How to get the path of folder and files?

This post documents my learning through my struggle.

Ways of iterating over the map

It really depends on whether you need the keys, the values or both. In Golang, there would be one way of iterating over maps

for k, v := range xs { // drop the variable k or v if only one of them is necessary
    ...
}

However, there seems to be so many ways of doing it in Java

// Modern way of doing it
for (Entry<K, V> e: m.entrySet()) {
    f(e.getKey(), e.getValue());
}

for (V value: m.values()) {}
for (K key: m.keySet()) {}
// The safe way if you want to remove things mid-iteration
// For older version of java
Iterator it = m.entrySet().iterator();
for (it.hasNext()) {
    Map.Entry p = (Map.Entry)it.next();
    ...
    it.remove(); // Avoids concurrent modification exception
}

It seems like a gotcha in Java to not be able to do this

package Map;

import java.util.HashMap;
import java.util.Map;

public class MyMap {
    public static void main(String[] args) {
        Map<String, Integer> m = new HashMap<>();
        m.put("a", 1);
        m.put("b", 2);
        m.put("c", 3);
        m.put("d", 4);
        for (Map.Entry<String, Integer> e : m.entrySet()) {
            System.out.println(e.getKey());
            if (e.getValue() %2 == 0) {
                m.remove(e.getKey());
            }
        }
    }
}
package main

import (
    "fmt"
)

func main() {
    m := map[string]int{
        "a": 1,
        "b": 2,
        "c": 3,
        "d": 4,
    }
    for k, v := range m {
        if v%2 == 0 {
            delete(m, k)
        }
    }

    fmt.Printf("%v", m)
}

Higher order functions

One thing I really like about programming languages is the support for higher order functions. That is being able to treat functions as a value and pass them around.

Java supports this through the Function<A,B> class, which was a fairly new feature.

A common concept is delayed evaluation. Where the function is evaluated only at call site instead of at definition.

Instead of f: A, we want f: () => A

In golang, there is one straight forward way to do it.

func () A {...}

In Java, there are several ways, depending on what you want to do.

For example, if you wanted a lazy function, it would be rather awkward to define it as Function<Void, A>.

static Function<Void, Integer> f = () -> 9; // Will not compile
static Function<Void, Integer> f = (Void v) -> 9; // This is ok
...
f.apply(null); // Ugly but works

The correct class is Supplier<A>. The adage goes as follows:

  1. Use Supplier<A> if the method takes no argument.
  2. Use Consumer<A> if the result returns value.
  3. Use Runnable if both method and results are void.
  4. Use Predicate<T> is the same as Function<T,Boolean>. c.f. list.removeIf()
  5. UnaryOperator is the same as Function<T,T>. c.f. list.replaceAll()

The interesting thing to note here is how Runnable, typically grouped with Callable to be used alongside Threads, is used to express the type () => ().

Concurrency

Another useful pattern is concurrency. For example, starting a blocking call to wait for user input, or watching for file change. Very seldom should the main thread be blocked.

func main() {
    go func() {
        doBlocking()
    }()
    doMainStuff()
}

In java, it is slightly nicer now with lambdas.

public static void main(String[] args) {
    // This is one way of doing it
    new Thread(() -> {
        // Do long stuff
    }).start();

    // This way of doing it takes care of retrying the task automatically
    Executors.newSingleThreadScheduledExecutor().scheduleWithFixedDelay(() -> {
        try {
            f();
        } catch (Exception e) {
            System.out.println("Failed, but we'll try again");
            System.out.println(LocalTime.now());
        }
    }, 0, 3, TimeUnit.SECONDS);
    // Failed, but we'll try again
    // 01:00:03.432528
    // Failed, but we'll try again
    // 01:00:06.440588400
    // Failed, but we'll try again
    // 01:00:09.441395400
    // Failed, but we'll try again
    // 01:00:12.442236700
}

public static void f() throws Exception {
    throw new Exception("Foo");
}

Joining paths and getting variables

It is very common to want to pass in run time parameters to control program flow. Typically, this is done via program arguments, flag values or environment variables.

As a motivating example, suppose we want to create a config directory to store files for our program. A home directory is reasonable, but how do we take into account the different user environments?

The JVM injects certain default platform aware properties and this makes the task bearable.

public static void main(String[] args) {
    Path p = Paths.get(System.getProperty("user.home"), "code");
    System.out.println(p);
}

When I first saw the parameters to starting a Java program, I was daunted by the many (unreadable) flag values such as -Dfoo. Only when I started playing around with production code did I realize they were essentially passing in system properties. The following snippet is how one would access that value passed in this way.

System.out.println(System.getProperty("foo"));

Conclusion

Programming in Java is less painful now, as long as you fill the void.