How many of these points in the definition of a first-class element do Java's functions satisfy?

417 Views Asked by At

Structure and Interpretation of Computer Programs gives the following as the conditions that an element of a programming language must satisfy to be considered first-class:

  1. They may be named by variables.

  2. They may be passed as arguments to procedures.

  3. They may be returned as the results of procedures.

  4. They may be included in data structures

How many of these do Java's functions satisfy? If there's any ambiguity, e.g. "does putting a function in an object count for #4?", then please mention that in your answer.

3

There are 3 best solutions below

6
Turing85 On

Functions in Java do not have a defined type. We can write things like Objects::nonNull, but this, in its core, is just syntactic sugar for or (foo) -> Objects.nonNull(foo), which will produce an instance of a functional interface (see JLS, §15.13 and §15.27 respectively). So the methods are always wrapped in an (possibly anonymous) interface implementation.

If we compare this to languages like C++, where every function has a defined type, we see that Java lambdas do not satisfy any of this criteria.

6
kaya3 On

This is debatable. Certainly you can write something like this:

Function<Integer, Integer> times2 = x -> x * 2;

Is this naming a function by a variable? Well, yes and no. The variable times2 holds a reference to a "function", so the "function" is a value and is named by the variable. On the other hand, the Java Language Specification says this:

A variable of an interface type can hold a null reference or a reference to any instance of any class that implements the interface.

That is, the variable actually does not hold a reference to a function directly; it holds a reference to an object which has an apply method. The JLS doesn't allow for it to hold "a function" or "a reference to a function", only "a reference to an instance" which satisfies the interface. The instance has a method named apply, which executes the function when it is called.

Can we say that the object is the function? Well, yes and no, it's a matter of interpretation. But I would lean towards "no", because the name of the method apply is specific to the Function interface, and if you wrote the same lambda function in a different context then the "function object" could have a method with a different name. If the same lambda function is written in two different contexts, is it the "same function"? I would argue yes, but clearly they are not the same objects because they have different method names. So I conclude that if they are the same function but not the same object, then the function and the object are different things.


Note that @rzwitserloot's answer defending the opposite interpretation is also correct, except insofar as they assert their interpretation is the only possible one. As I said, it is debatable. I should think the fact we are debating it is evidence enough for that proposition...

11
rzwitserloot On
  1. They may be named by variables.

Yes, clearly:

Runnable r = () -> System.out.println("Hello");
Runnable r2 = System.out::println;

I have functions / method references, and I can have named variables that refer to them.

AMBIGUITY:

These variables could also point at actual objects instead:

r = new Runnable() {
    public void run() {
        System.out.println("Goodbye!");
    }
};

Unlike the function syntax example, the above means r really is pointing at a definite object with defined object-like characteristics. There is a real object in memory and the java lang spec guarantees this.

However, that doesn't matter. In a language like, say, javascript or python, I can assign anything to a variable (variables are untyped in these languages). That doesn't 'make all variables booleans' just because I can assign true and false to any variable.

Similarly, in java, to invoke the function, e.g. to actually print Hello given: Runnable r = () -> System.out.println("Hello");, I'd have to write r.run() and not r().

That's a debate on syntax, it's not something fundamental. In python, r() will take the variable r, will dereference it (follow the pointer), try to interpret what it finds there as a function, and execute it, passing no arguments. If it can't be coerced to a function, an error occurs.

Java is no different. r.run() will take the variable r, will dereference it (follow the pointer), try to interpret what it finds there as a function of type Runnable, and executes it, passing no arguments. If what it finds there is not a Runnable (for example, it is null), an error occurs.

See? identical. Trying to define things in terms of how other languages do things would incorrectly lead one to say that somehow the above is 'syntax sugar' that 'does not count', which is an incorrect conclusion.

  1. They may be passed as arguments to procedures.

Yes, unambiguously so.

public void runTwice(Runnable r) {
    r.run();
    r.run();
}

Runnable r = () -> System.out.println("Hello");
runTwice(r);
runTwice(System.out::println);
runTwice(() -> System.out.println("Goodbye!"));

I am passing these functions to java.

  1. They may be returned as the results of procedures.

Yes, unambiguously so:

public Runnable printMeTwice(String text) {
    return () -> {
        System.out.println(text);
        System.out.println(text);
    };
}

Runnable r = printMeTwice();
r.run();
  1. They may be included in data structures

Yes, unambiguously so:

class Animal {
    String name;
    Function<Food, Excrement> eat;
}

Animal animal = new Animal();
animal.eat = food -> return food.extractNutrients();

If there's any ambiguity.

Using the specific wording as you pasted it, there is no ambiguity at all, which is somewhat interesting, considering that this question already has answers that either misunderstood or use particularly bizarre interpretations of these words.

One 'weirdness' where java is a little different compared to most other languages is that in most languages, if variable r holds a function, to do the job of 'dereference the pointer and then execute the function you find when you do so', you'd write r(). In java you don't do that; instead in java you do r.run() or r.apply(t); in java, functions have named types whereas in most languages they don't. For example, in java I can have the concept of an 'integers-only calculator operation':

IntCalcOp plusButton = (a, b) -> a + b;

and I can have the concept of a 'integer comparing function':

IntComparator highestIsEarlier = (a, b) -> b - a;

and these two things are just fundamentally different. I can't pass a variable of type IntCalcOp to a method that requires an IntComparator, even though the signatures are functionally entirely identical - both take 2 ints and return an int.

Note that this isn't inherently inferior; in fact, it is probably inherently superior. It's a difference of opinion in language design. To show the benefits of nominal typing and complete lack of implicit conversion, here are 2 very simple object definitions that are functionally identical:

interface Camera {
    void shoot(Person p);
}

interface Gun {
    void shoot(Person p);
}

In some languages like python and javascript, if I give you a gun and you expected a camera, you kill somebody. In java, that can't happen.

No amount of blathering on about 'but it is syntax sugar' changes any of the 4 snippets above that clearly show that Java's functions are first class elements as per the definition of Structure and Interpretation of Computer Programs.

I don't think that book has a little * with a footnote that states: "Syntax sugar does not count". If you care to operate with that caveat in place, we'd first need a definition of what syntax sugar even means.

For example, the java language spec does not say that these are turned into anonymous inner class literals. Implementations don't even do that anymore (other answers mention this, and are incorrect in that regard).

A C compiler may work by first emitting assembler code and then running that through an assembler to produce an executable. Does that make all of the C code just 'syntax sugar'? If that's true, C doesn't even have loops.

That kind of reasoning is interesting but for the purposes of determining what a language can do, completely pointless, I think. Essentially, all programming languages are 100% syntax sugar if you dig deep enough.