IMO...
- Because Java and C# are not true OO languages.
- Functional programming was not in vogue when they were designed.
I agree with Jörg W Mittag, neither C# nor Java are true object-oriented languages. They're hybrid procedural/OO languages attempting to improve on C++ (and C# attempting to improve on Java). They have both a traditional primitive type system and a class system. For example, int
is a primitive type in Java and requires a wrapper to behave like an object.
Such hybrid design means the language designers get to pick and choose what is and is not an object. The designers of Java and C# didn't feel functions needed to be objects, I'm guessing because functional programming wasn't vogue back then and it was a little faster to not have them be objects. So they weren't objects.
In contrast, true object-oriented languages (Smalltalk, Ruby, Scalar, Eiffel, Emerald, Self, Raku) treat everything as an object which responds to methods. Everything. That includes methods and procedures. They're objects so they can be referenced. Methods being objects are inherent to OO design because everything is an object; the language designers would have to deliberately do otherwise.
For example, Ruby is a pure OO language. Since everything is an object, it has Method objects. Since everything is an object, the code of a Method is a Proc object. Lambdas are just special Procs.
func = lambda { |x | x**2 }
That's syntax sugar for Proc.new
. func is an instance of Proc. I can call methods on it.
p func.call(4) # 16
p func.class # Proc
I can ask it for its call method. That is a Method object.
method = func.method(:call)
p method.class # Method
p method.call(4) # 16
Anonymous functions and method references are a benefit of Ruby sticking to OO design principles.
In contrast, let's look at how Java and C# implemented lambdas and function references.
Java was designed in early 90s as a better C++. C++ is a hybrid procedural/OO language with many, many design problems, but it was extremely popular and influenced what many people thought OO was. Java inherited these problems.
Because Java is a hybrid language, they picked and chose what was and was not an object. For whatever reason, probably micro-optimization and a lack of appreciation for functional programming, they decided that methods were not objects and could not be referenced, and there were no bare functions.
Java attempted to address the need to pass around snippets of functionality with anonymous classes. They finally realized this is awkward and added lambda expressions explaining...
Lambda expressions let you express instances of single-method classes more compactly.
One issue with anonymous classes is that if the implementation of your anonymous class is very simple, such as an interface that contains only one method, then the syntax of anonymous classes may seem unwieldy and unclear. In these cases, you're usually trying to pass functionality as an argument to another method, such as what action should be taken when someone clicks a button. Lambda expressions enable you to do this, to treat functionality as method argument, or code as data.
At the same time they added method references...
You use lambda expressions to create anonymous methods. Sometimes, however, a lambda expression does nothing but call an existing method. In those cases, it's often clearer to refer to the existing method by name. Method references enable you to do this; they are compact, easy-to-read lambda expressions for methods that already have a name.
What you can do is use them to construct instances of any interface which is a FunctionalInterface.
Functional interfaces provide target types for lambda expressions and method references.
Runnable r = () -> System.out.println("Hello World!");
// The equivalent Runnable class.
Runnable r = new Runnable() {
@Override
public void run() {
System.out.println("Hello World!");
}
};
There's dozens of FunctionalInterfaces which seem to be working around Java's non-OO primitive types.
DoubleToIntFunction: Represents a function that accepts a double-valued argument and produces an int-valued result.
The hybrid nature of Java means rather than flowing naturally from the design, a complex series of adapters is necessary.
Point is, it was bolted on later and it's complicated.
C# was designed a better Java. It fixed some mistakes, and repeated others.
When you go back and look, C# version 1.0, released with Visual Studio .NET 2002, looked a lot like Java. As part of its stated design goals for ECMA, it sought to be a "simple, modern, general-purpose object-oriented language." At the time, looking like Java meant it achieved those early design goals.
C# 3.0 added lambda expressions. As with Java, the relationship between lambda expressions and objects is complicated...
Any lambda expression can be converted to a delegate type. The delegate type to which a lambda expression can be converted is defined by the types of its parameters and return value. If a lambda expression doesn't return a value, it can be converted to one of the Action delegate types; otherwise, it can be converted to one of the Func delegate types. For example, a lambda expression that has two parameters and returns no value can be converted to an Action<T1,T2> delegate. A lambda expression that has one parameter and returns a value can be converted to a Func<T,TResult> delegate.
This complex system of annotation types and target types and automatic casting and blurring between what is and is not an object is a common problem of hybrid languages, particularly C++ derived languages which mix procedural and object-oriented principles with a C-style type system without choosing one clear paradigm.
And that's my point. True object-oriented languages naturally have function references because functions are objects. Hybrid languages get to pick and choose what is and is not an object; if the language designer didn't think function references were needed, you don't get function references.
apply
is essentially a function. Which is pretty much how functions in Java work: any type which …Collections.sort
, there is noRunnable
in sight. And there is certainly no "companion object", at least not how I understand the term.