4

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.

J. Mini
  • 1,868
  • 1
  • 9
  • 38
  • 1
    Here is the thing: functions in Java do not have a defined type. We can write things like `Objects::nonNull` or `someObject.getClass()::isInstance`, but this, in its core, is just syntactic sugar for `(foo) -> Objects.nonNull(foo)`. And this, in return, is just syntactic sugar for an anonymous interface implementation. If we compare this to languages like C++, where every function has a defined type, and we see that Java lambdas do not satisfy any of this criteria. – Turing85 Aug 22 '21 at 11:55
  • I'd say the anonymous interface implementations are an implementation detail, and for practical purposes all of the can be accomplished with lambdas. But since we obviously disagree about this, I'd actually close the question as opinion-based. – Clashsoft Aug 22 '21 at 11:57
  • The "defined type" is also practically irrelevant, since you have to define a type for everything in Java (ignoring the newer `var`), and there are types for all kinds of function arities. – Clashsoft Aug 22 '21 at 11:59
  • @Turing85 It's not just syntactic sugar, at least in interpreted mode (before the JIT optimized it). `o -> Objects.nonNull(o)` will create a `new` lambda, whereas `Objects::nonNull` ist just a reference to an existing one. Though *in theory* `Objects::nonNull` should be faster and more memory efficient. But of course the main goal should be to write readable code. So choose whatever fits your needs best. The JVM will do the rest for you :) – Benjamin M Aug 22 '21 at 12:00
  • @Clashsoft A type should be unambiguous. As I said: in C++, we can derive the type of a function by its definition. We cannot do this in Java. A single method reference could implement different functional interfaces (admittedly, all of them have the same or very similar signatures), so it is not clearly defined. – Turing85 Aug 22 '21 at 12:02
  • 1
    @Turing85 but where in question or the original list of conditions did you find anything about an unambiguous defined type? Or in other words, why do you need one to fulfill these conditions? – Clashsoft Aug 22 '21 at 12:16
  • @Clashsoft how do you want to return something that does not have a type? How do you want to pass something along, that does not have a type? We have to create those types artificially (through interfaces). They are not directly native to the language, hence they are not 1st class citizens. IIRC, there is also not a way to "compare" lambdas (at least no consistent way), which should also be possible with variables. – Turing85 Aug 22 '21 at 12:18

3 Answers3

4

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...

kaya3
  • 47,440
  • 4
  • 68
  • 97
  • _does not hold a reference to a function directly; it holds a reference to an object which has an apply method._ That's not really true; `times2` has no identity here, and `times2` is, more or less, a method handle. If you bend over backwards and attempt to treat `times2` as an object (e.g. you try to lock on it, or pass it to `System.identityHashCode`, then the JVM will make a proxy object for you, though the answers you get when using `times2` as an object will not be useful. – rzwitserloot Aug 22 '21 at 12:33
  • @rzwitserloot That's an implementation detail. Semantically, a variable of type `Function` holds a reference to an instance of some class which implements the `Function` interface (or `null`), just like a variable of type `List` holds a reference to an instance of some class which implements the `List` interface (or `null`). – kaya3 Aug 22 '21 at 12:37
  • Similarly, it would be possible for Java to implement the boxed `Integer` type by storing either a pointer to an instance (tagged as a pointer) or a primitive value (tagged as a primitive value), and create `Integer` instances on the fly only when they are used for something other than arithmetic. This optimisation would not change the semantics of the language, which say that a variable of type `Integer` holds a reference to an instance of `Integer` (or `null`). – kaya3 Aug 22 '21 at 12:42
  • No, you're just incorrect here. It does not hold an instance of any class. Period. Go run a debugger and see what the VM is actually doing. The java lang spec explains what's supposed to happen if you attempt to treat an expression of type Function as an object, e.g. if you go: `Function x = ...; synchronized (x) {}`, but that doesn't make it so. If it did make it so, then I can state that in javascript, all variables are booleans. After all, I can write `if (x)` for any expression and it isn't a syntax error, QED. – rzwitserloot Aug 22 '21 at 12:50
  • @rzwitserloot "Go run a debugger and see what the VM is actually doing" - i.e. you are talking about *the implementation*. The JLS defines the semantics of the language without reference to what you should or shouldn't see when using a debugger. – kaya3 Aug 22 '21 at 12:56
  • 1
    The JLS [explicitly says](https://docs.oracle.com/javase/specs/jls/se8/html/jls-4.html#jls-4.12.2) *"A variable of an interface type can hold a null reference or a reference to any instance of any class that implements the interface."* So according the JLS, a variable of type `Function` *cannot* hold something other than either an instance of a class that implements that interface, or `null`. There is no way around that. "But the implementation" is not a way around it. We are talking about the semantics of the language. – kaya3 Aug 22 '21 at 12:59
2
  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.

rzwitserloot
  • 85,357
  • 5
  • 51
  • 72
  • *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.* - I think you are totally missing the point of my answer. The ambiguity lies only in the word "function", as in whether the function *is* the value or whether the function is merely a *property* of the value. All of your points are correct except you have asserted that they are *unambiguously* correct, i.e. you deny that it is even *possible* to ... – kaya3 Aug 22 '21 at 12:53
  • ... interpret it as anything other than "the function is the value". I think it is pretty clear that there is another viable interpretation, "the function is a property of the value", and the purpose of my answer is to justify that as a *possible* interpretation. – kaya3 Aug 22 '21 at 12:55
  • @kaya3 I've added a few words on how one might draw that conclusion when adopting a javascript/python-esque worldview on how programming languages should work. Hopefully this addresses your concern. – rzwitserloot Aug 22 '21 at 12:57
  • It does not address my concern, and I think your point about syntax sugar is disingenuous, since the purpose of syntax sugar is to define the semantics of a language feature in terms of other features *of the same language*. So Python code cannot be syntax sugar for assembler or C, because Python is not assembler or C. – kaya3 Aug 22 '21 at 13:01
  • I'm still not sure about *"4. They may be included in data structures"*, because the function is not "embedded" in the data structure, but instead the data structure will just contain a reference to the function. Not really sure if that counts as "included". But on the other hand I don't know if it could be even possible to have a function without a reference in other languages (or when Project Valhalla and Value Types go live). I'm confused... – Benjamin M Aug 22 '21 at 13:05
  • @BenjaminM That is how inclusion of a value in a data structure works; if the value is of reference type, then the data structure holds a reference; the reference *is* the value, the thing being referenced is not a value in that sense. – kaya3 Aug 22 '21 at 13:12
  • @kaya3 Yeah. That was my point: In Java a data structure can only hold a reference to a function, but not the function itself. Though I'm not sure if that means "the data structure includes a function". Or an highly exaggerated example: *If my data structure includes an HTTP client, does the data structure include the internet?* I don't know where to draw the line: Does "included" mean that it has to be physically inside the data structure (i.e. within the allocated memory of the object itself). Or is a reference sufficient to make the referenced thing "included" within the data structure? – Benjamin M Aug 22 '21 at 13:23
  • 1
    @BenjaminM To clarify just this point, the idea that functions are reference values so that the value of a function-valued variable *is* a reference to the function, is totally compatible with SICP's definition of what it means for a function to be a value. Nobody would say that Python doesn't have first-class functions merely because Python's variables always hold references, for example. – kaya3 Aug 22 '21 at 13:25
  • "*These variables could also point at actual objects instead*" - [Reference-type variables always reference either an instance of a class, or `null`](https://docs.oracle.com/javase/specs/jls/se16/html/jls-4.html#jls-4.12.2). – Turing85 Aug 22 '21 at 13:34
  • @BenjaminM _but instead the data structure will just contain a reference to the function. Not really sure if that counts as "included"._ - it's unambiguous, if you think a little longer: Very few languages allow direct embedding. Neither does java. So if we go by that definition, then javascript, java, scala, PHP, python, and ruby all have no first-class functions. Or first-class objects or even first-class Strings. That is a _crazy_ conclusion, clearly that's not what the book is trying to indicate. Or, if it is, the book is a stupid book you should throw out. Let's not assume the worst :P – rzwitserloot Aug 22 '21 at 15:23
  • @rzwitserloot In Java we can at least embed primitive values. And with Project Valhalla Value Types it should be possible to embed any kind of objects (don't know if functions will be treated the same). But yeah, you both are right: If we assume that `reference != included`, then most languages would have very few (or even none) first-class constructs :D – Benjamin M Aug 22 '21 at 15:45
1

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.

Turing85
  • 18,217
  • 7
  • 33
  • 58
  • And "anonymous interface implementation"(s) is just semantic sugar for an "anonymous class implementing said interface" :-D – JayC667 Aug 22 '21 at 12:12
  • 1
    This is implied by the word "*implementation*", isn't it? =) – Turing85 Aug 22 '21 at 12:13
  • _And this, in return, is just syntactic sugar for an anonymous interface implementation._ This is incorrect. Run `javap` on an anonymous inner class def vs. one of these and you'll see the difference. – rzwitserloot Aug 22 '21 at 12:29
  • This answer is in fact wholly incorrect. For example, java 'functions' can be trivially assigned to variables: `Runnable r = System.out::println; r.run();` both compiles and runs and does what you'd think it does. "But, it's all syntax sugar!".. so? C is 'just syntax sugar for becoming assembler code, so by that definition, C isn't even a programming language'. – rzwitserloot Aug 22 '21 at 12:31
  • 1
    The JLS says this: *"Evaluation of a lambda expression produces an instance of a functional interface (§9.8)."* and this: *"At run time, evaluation of a lambda expression is similar to evaluation of a class instance creation expression, insofar as normal completion produces a reference to an object."* The `javap` command-line tool is not a higher authority than the Java Language Specification. https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.27 What the `javap` output shows is only that the compiler doesn't implement it naively, not that the semantics are different. – kaya3 Aug 22 '21 at 13:20
  • 1
    @rzwitserloot I removed the part about the syntactic sugar and included the relevant JLS section kaya3 provided. Wrt. to your statement of C being syntactic sugar for becoming assembly... the question is language based (in this case: Java). So my argument is based on what the language supports, not on what the compiler produces. – Turing85 Aug 22 '21 at 13:30