如何用lambda演算术语定义匿名函数(或如何说某些语言支持匿名函数)? [英] How do you define an anonymous function in lambda calculus terms (or how can I say some language supports anonymous functions)?

查看:85
本文介绍了如何用lambda演算术语定义匿名函数(或如何说某些语言支持匿名函数)?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

Java在当前版本的6个lambda表达式或匿名函数中是否支持?在Java中,有什么我无法使用支持lambda表达式的编程语言来完成的吗?我知道Java即将完成,因此您可以在其中进行任何操作。

Does java support in current version of 6 lambda expressions or "anonymous functions"? Is there something I can't do in java that I couldn't do with a programming language supporting lambda expressions? I understand that java is turing complete so you can do "anything" in it.

为什么匿名内部类包装函数不能表示lambda演算中定义的函数?

Why couldn't anonymous inner class wrapped functions represent functions as defined in lambda calculus?

实际上是什么匿名函数,以及如何说某种语言支持匿名函数?

What actually is an anonymous function and how you can say that some language supports anonymous functions?

推荐答案

正如我在上面的评论中所暗示的那样,问题实际上取决于您如何精确定义支持。在您的问题中,您已经提到Java是图灵完备的,因此Java支持(对于支持的某些定义)所有其他编程语言都支持的所有内容。

As I already hinted at with my comment above, the question really hinges on how exactly you define "support". In your question you already mentioned that Java is Turing-complete, and thus Java "supports" (for some definition of "supports") everything that every other programming language supports.

Java 确实支持匿名函数:只需在Java中为λ -calculus编写解释器,然后将匿名函数作为字符串传递即可。

Java does support anonymous functions: just write an interpreter for the λ-calculus in Java and pass the anonymous function in as a string.

但是,我发现使用匿名函数的工作量过多。因此,对我而言,有趣的问题不是Java是否支持匿名函数,而是当我想使用匿名函数时Java是否支持 me 。 IOW:Java是否简化了匿名函数的使用,对我有帮助,对我有帮助吗?

However, I for one find that too much work for using an anonymous function. So, for me the interesting question is not so much whether Java supports anonymous functions but whether, when I want to use anonymous functions, Java supports me. IOW: does Java make it easy to use anonymous functions, does it guide me, does it help me?

让我们做一个简单的实验:实现 map 函数,并使用它使列表 [1、2、3、4、5] 的每个元素增加 1

Let's make a simple experiment: implement the map function and use it to increment every element of the list [1, 2, 3, 4, 5] by 1.

以下是<$ c $的实现方式c> morph (这是我将要调用的函数,以便不与已经存在的内置 map 函数发生冲突)在Haskell中:

Here's how the implementation of morph (which is what I'm going to call the function in order to not collide with the already existing builtin map function) looks like in Haskell:

morph _ []     = []
morph f (x:xs) = f x : morph f xs

仅此而已。简短而有趣:将空列表变形为空列表只是将空列表变形,而将具有至少一个元素的列表变形为将变形功能应用于第一个元素,并将其与变形其余列表的结果相连接。

That's all. Short and sweet: morphing the empty list with anything is just the empty list, and morphing a list with at least one element is applying the morphing function to the first element and concatenating that with the result of morphing the rest of the list.

如您所见,编写一个接受函数作为参数的函数非常简单,轻便。

As you can see, writing a function which accepts a function as an argument, is very easy, very lightweight.

假设我们有一个列表 l

l = [1, 2, 3, 4, 5]

我们现在可以像这样调用morph了:

We can now call morph like so:

morph (\x -> 1 + x) l

再次,传递给我们的高阶函数非常简单,轻巧。

Again, passing an anonymous function to our higher-order function is very easy, very lightweight.

它看起来几乎像λ-演算。实际上,如果您使用Haskell IDE,具有Haskell模式的文本编辑器或Haskell漂亮打印机,则实际上它会像这样显示:

And it looks almost like λ-calculus. In fact, if you use a Haskell IDE, a text editor with a Haskell mode or a Haskell pretty-printer, it will actually be displayed like this:

morph (λx → 1 + x) l

如果我们使用 operator部分,这使我们可以传递部分应用的运算符:

It gets even easier if we use an operator section, which allows us to pass a partially applied operator:

morph (1+) l

或者,我们可以通过预定义的 succ 函数,该函数返回整数的后继:

Or, we can pass the pre-defined succ function which returns the successor of an integer:

morph succ l

尽管这当然不是匿名函数,但它是一个命名函数。

Although this is of course not an anonymous function, it is a named one.

在Scala中,它看起来非常相似。主要区别在于Scala的类型系统比Haskell的类型系统复杂,因此需要更多类型注释:

In Scala, it looks very similar. The main difference is that Scala's type system is more complex than Haskell's and thus requires more type annotations:

def morph[A, B](l: List[A])(f: A => B): List[B] = l match {
  case Nil     => Nil
  case x :: xs => f(x) :: morph(xs)(f)
}

它仍然非常轻巧。本质上,我们要做的就是声明 f 参数的类型为 A =>。 B (即从 A 类型到 B 类型的函数),实际上是语法 Function1 [A,B] 的糖。

It is still very lightweight. Essentially, all we had to do was to declare the f parameter to be of type A => B (i.e. a function from type A to type B), which is actually syntactic sugar for Function1[A, B].

现在我们只需要我们的列表:

Now we just need our list:

val l = List(1, 2, 3, 4, 5)

并将其变形:

morph(l) {_ + 1}

这又利用了Scala的一些语法糖。在匿名函数中,可以省略参数列表。如果只按定义的顺序使用每个参数一次,则可以简单地将它们称为 _

This takes again advantage of some of Scala's syntactic sugar. In anonymous functions, you can leave off the parameter list; if use every parameter exactly once and in the order they are defined, you can simply refer to them as _.

但即使是完整格式也没有那么重:

But even the full form is not much heavier:

morph(l) {(e) => e + 1}

如果我遇到了使 morph 某个类的实例方法,并根据 Pimp My Library 模式定义了从 List 到该类的隐式转换,我什至可以写类似

If I had gone through the trouble of making morph an instance method of some class and defined an implicit conversion from List to that class according to the Pimp My Library pattern, I could have even written something like

l morph {_ + 1}



Scheme



当然,匿名和高阶函数应该没有问题。这是 morph

(define (morph f l)
    (if (null? l)
        null
        (cons
            (f (first l))
            (morph f (rest l)))))

这是我们的列表:

(define l '(1 2 3 4 5))

我们的匿名函数用法:

(morph (lambda (e) (+ e 1)) '(1 2 3 4 5))



Ruby



Ruby

module Enumerable
  def morph
    [].tap {|r| each {|e| r << yield(e) }}
  end
end

这是 非常轻巧。我们甚至不必为函数定义参数,因为在Ruby中,每个方法都有一个隐含的函数参数,称为 block

This is extremely lightweight. We didn't even have to define a parameter for the function, because in Ruby, every method has an implied function parameter, called a block.

l = [1, 2, 3, 4, 5]

调用它几乎像Scala一样轻巧

Calling it is almost as lightweight as Scala

l.morph {|e| e + 1 }

我可以通过抓取引用来从Haskell示例中复制运算符部分到 1 + 方法:

I can sort-of replicate the operator sections from the Haskell example by grabbing a reference to the + method of 1:

l.morph(&1.method(:+))

Ruby还为整数提供了预定义的 succ 方法,我们可以使用 Symbol#to_proc 技巧进行传递:

Ruby also has a pre-defined succ method for integers which we can pass using the Symbol#to_proc trick:

l.morph(&:succ)

有人批评Ruby中的块,因为每个方法只能包含一个块,而具有多个功能的方法要难看得多,但实际上并不是 坏。这与上面的代码相同,但是没有使用块:

Some people criticize blocks in Ruby, because every method can only take a single block, and methods taking more than one function are much uglier, but it's actually not that bad. Here's the same code as above, but without using blocks:

module Enumerable
  def morph(f)
    [].tap &-> r { each &-> e { r << f.(e) }}
  end
end

l = [1, 2, 3, 4, 5]

l.morph -> e { e + 1 }
l.morph(1.method(:+))



ECMAScript(2015年之前)



ECMAScript是Scheme的直接后代,因此尽管语法有一些混乱,但它可以很好地处理我们的问题也就不足为奇了:

ECMAScript (pre-2015)

ECMAScript is a direct descendant of Scheme, so it's no surprise that it can handle our problem nicely, although with some amount of syntax clutter:

Array.prototype.morph = function (f) {
    var r = [];
    this.forEach(function (e) { r.push(f(e)); });

    return r;
}

这里主要的干扰是一般的丑陋语法,而不是处理

The main distraction here is the generally ugly syntax, not so much the handling of higher-order functions.

让我们建立我们的列表(好,数组):

Let's build our list (well, array):

var l = [1, 2, 3, 4, 5];

并调用 morph 函数(实际上,

And call the morph function (actually, a method in this case) passing an anonymous function as an argument:

l.morph(function (e) { return e + 1; });



ECMAScript(2015年后)



ECMAScript 2015引入了胖箭头匿名函数文字

Array.prototype.morph = f => {
    const r = [];
    this.forEach(e => r.push(f(e)));

    return r;
}

让我们构建列表(好,数组):

Let's build our list (well, array):

const l = [1, 2, 3, 4, 5];

并调用 morph 函数(实际上,在这种情况下是一个方法)将匿名函数作为参数传递:

And call the morph function (actually, a method in this case) passing an anonymous function as an argument:

l.morph(e => e + 1);



C#



现在我们要移动更接近我们的最终目标语言。这是C#:

C#

Now we're moving closer to our ultimate target language. Here's C#:

public static IEnumerable<B> Morph<A, B>(this IEnumerable<A> l, Func<A, B> f)
{
    IList<B> r = new List<B>();
    foreach (var e in l) r.Add(f(e));

    return r;
}

还不错。请注意函数的类型: Func< A,B> 。这是预定义类型,它是核心库的一部分,就像Scala中的 Function1 [A,B] a→b 在Haskell中。 (这是与Java的重要区别。)

Not too bad. Note the type of the function: Func<A, B>. This is a pre-defined type which is part of the core library, just like Function1[A, B] in Scala or a → b in Haskell. (This is an important distinction to Java.)

感谢键入推断和集合初始值设定项,创建列表并不十分麻烦:

Thanks to type inference and collection initializers, creating the list isn't all too painful:

var l = new List<int> { 1, 2, 3, 4, 5 };

传递仅包含一个表达式的lambda基本上和Ruby,Scala,Scheme一样轻便或Haskell甚至比ECMAScript更轻巧,因为您不需要函数 return 关键字:

And passing a lambda which consists only of a single expression is basically as lightweight as Ruby, Scala, Scheme or Haskell and even more lightweight than ECMAScript, because you don't need the function or return keywords:

l.Morph(e => e + 1);

但是即使使用完整语法也不是太糟糕:

But even using the "full" syntax isn't too bad:

l.Morph((e) => { return e + 1; });

(您会注意到我制作了 Morph 扩展方法,这意味着除了 Morph(l,f) l.Morph(f)这样称呼它$ c>。)

(You'll notice that I made Morph an extension method, which means I can call it like l.Morph(f) in addition to Morph(l, f).)

static <A, B> List<B> morph(List<A> l, Function1<A, B> f) {
    List<B> r = new ArrayList<>();
    for (A e: l) r.add(f.apply(e));

    return r;
}

乍一看,实际上还不错。实际上,它看起来非常类似于C#版本。但是为什么我不能写 f(e)?为什么我必须写 f.apply(e)?在所有其他语言中,我可以使用与调用任何其他函数,过程或方法相同的语法(对于Ruby,几乎是相同的)来调用作为参数传递的函数。

At first glance, this isn't too bad, actually. In fact, it looks pretty much exactly like the C# version. But why can't I write f(e)? Why do I have to write f.apply(e)? In all other languages I could use the same (or in the case of Ruby, almost the same) syntax for calling a function that was passed as an argument as I would for calling any other function, procedure or method.

我知道数量不多,但是这种苦涩的味道使功能不算是一流的。而且,正如我们将进一步看到的那样,在此过程中的每一步都有一些小的烦恼,即使它们中的每一个都不重要,它们也会

I know it isn't much, but it leaves this kind of bitter taste that somehow functions aren't quite first-class. Also, as we'll see further on, there is one of those small annoyances at every single step along the way, and even though every single one of them is insignificant by itself, they do add up.

这是我们的列表:

List<Integer> l = Arrays.asList(1, 2, 3, 4, 5);

这就是我们所说的 morph

morph(l, new Function1<Integer, Integer>() {
    @Override public Integer apply(Integer n) {
        return n + 1;
    }
});

这是很重的东西。我的意思是,我要做的就是调用一个方法并传递两个参数。为什么会爆炸成四行?在所有其他语言中,这只是一个简单的代码。当然,我可以删除所有换行符,并且仍然有效:

This is pretty heavy stuff. I mean, all I'm doing is calling a method and passing two arguments. Why does that explode into four lines? In all other languages it was just a simple one-liner. Of course, I could remove all line breaks and it would still be valid:

morph(l, new Function1<Integer, Integer>() { @OVerride public Integer apply(Integer n) { return n + 1; }});

但是我想你明白我的意思了。正在执行的实际操作(将每个元素增加1)几乎在所有这些噪声之间是不可见的。

But I think you see what I'm getting at. The actual operation that is being performed, incrementing each element by 1, is pretty much invisible between all that noise.

还请注意,在我实际使用的其他一些语言中匿名函数 inside morph 函数的定义,例如在Ruby和ECMAScript中,这没什么大不了的。如果我要用Java做到这一点,那将导致更多的混乱和混乱,并增加行数。

Note also that in some of the other languages I actually used anonymous functions inside the definition of the morph function, for example in Ruby and ECMAScript and it was no big deal. If I were to do that in Java, it would lead to even more clutter and cruft and an explosion of lines.

所以,即使在这一点上,我们也看到了在Java中使用高级命令和匿名函数比在任何其他主流(而不是主流)语言中都更加麻烦。

So, even at this point we are seeing that working with higher-order and anonymous functions in Java is way more cumbersome than in pretty much any other mainstream (and not so mainstream) language.

但是我们还没有真正丑陋的部分: Function1< A,B> 类型是什么?

But we haven't even gotten to the really ugly part yet: what is that Function1<A, B> type there? Where did that come from?

嗯,我实际上不得不类型为我自己的东西!

Well, I actually had to write that type myself!

interface Function1<A, B> {
    B apply(A a);
}

当然,这就是所谓的 SAM接口,即具有单个
抽象方法
的接口或抽象类。这与Java具有的功能类型最接近。从某种意义上说,函数 只是一个具有单一方法的对象,因此非常好。通过SAM接口表示功能类型的事实也不是问题。实际上,这基本上就是它们在Scala中的表示方式(在Scala中, f(a)只是 f的语法糖。apply(a) ,因此具有 apply 方法的 any 对象本质上是一个函数),Ruby(在Ruby中, f。(a)只是 f.call(a)的语法糖,因此 every 调用方法本质上是一个函数),在C#中也类似。

This is, of course, a so-called SAM interface, i.e. an interface or an abstract class with a Single Abstract Method. Which is the closest thing to a function type Java has. In some sense, a function is just an object with a single method, so that's perfectly fine. The fact that function types are represented via SAM interfaces isn't the problem either. In fact, that's basically how they are are represented in Scala (in Scala, f(a) is just syntactic sugar for f.apply(a), so any object with an apply method is essentially a function), Ruby (in Ruby, f.(a) is just syntactic sugar for f.call(a), so every object with a call method is essentially a function) and similarly in C#.

问题是我必须编写它,那还不存在。

The problem is that I had to write it, that it wasn't already there.

我不仅必须自己写,而且还必须提出一个名称然后我必须为该方法命名。我与这里的任何其他语言都没有关系。好吧,实际上,我只是从Scala那里窃取了名称,因此实际的提出名称部分并不那么困难。

Not only did I have to write it myself, I had to come up with a name for it. And I had to come up with a name for the method. None of which I had to do with any of the other languages here. Well, actually, I just stole the names from Scala, so the actual "coming up with the names" part wasn't so difficult.

真正重要的是暗示,必须想出一个名字。 Java具有名义类型系统,即基于名称的类型系统。因此,我必须自己想出一个名字这一事实意味着其他所有人也都必须想出一个名字。而且由于他们的名称与我的不同(如果没有,则将导致编译错误),这意味着我无法将相同的函数传递给两个不同的库。举例来说,我想将相同的过滤功能传递给我的gridview和我的ORM。但是gridview希望使用单个方法 apply(T el) javax.swing.Predicate< T> >,而我的ORM则希望使用单一方法 apply(T el)使用 org.sleepy.Predicate< T>

What's really important are the implications of having to come up with a name. Java has a nominal type system, i.e. a type system based on names. And thus the fact that I had to come up with a name myself means that everybody else also has to come up with names. And because their names are different than mine (and if they aren't, then that will be a compile error), that means that I cannot pass the same function to two different libraries. Say, for example, I want to pass the same filtering function to my gridview and to my ORM. But the gridview expects, say, a javax.swing.Predicate<T> with a single method apply(T el) whereas my ORM expects an org.sleepy.Predicate<T> with a single method apply(T el).

请注意,这两种类型实际上完全相同,只是它们的名称不同,因此我无法将相同的函数传递给两个库。这不是一个假设的例子。在最近有关Java的Lambda项目的讨论中,有人计算了Java SE 6中已经存在 Predicate 类型的重复实例,而IIRC的数字是两位数

Note that these two types are really exactly the same, it's just that they have different names and thus I cannot pass the same function to both libraries. This is not a hypothetical example. During the recent discussions about Project Lambda for Java, someone counted how many duplicate instances of a Predicate type there were already in Java SE 6, and IIRC the number was in the double digits.

完全有可能在名义类型系统中解决此问题。毕竟,没有几十个 List 类型的不兼容副本,仅仅是因为Sun在库中放入了一个单个,每个人都使用那。他们可以使用 Function 来完成相同的操作,但事实并非如此,这导致相同,但互不兼容的类型不仅在第三方库中而且甚至在<在JRE中。 (例如, Runnable 可以说是一种函数类型, Comparator 也是如此。但是为什么它们必须特殊-在.NET中,它工作得很好,因为Microsoft在运行时中放置了一种类型。 (嗯,实际上不是一个单一的类型,但是足够接近。)

It is perfectly possible to solve this problem in a nominal type system. After all, there aren't dozens of incompatible copies of a List type, simply because Sun put a single one in the library, and everybody uses that. They could have done the same with Function, but they didn't, which leads to a proliferation of identical, yet mutually incompatible types not only in third-party libraries but even within the JRE. (For example, Runnable is arguably a function type, as is Comparator. But why do they have to be special-cased?) In .NET, it works just fine, because Microsoft put one single type into the runtime. (Well, actually not quite one single type, but close enough.)

因为在JRE中没有单一的函数类型,所以有也只有极少数采用函数类型的方法。这是使Java中难以使用一流和匿名函数的另一件事。一旦拥有一个,就无能为力了。您无法使用谓词功能过滤数组,无法使用映射功能转换列表,也无法使用比较器功能对网格视图进行排序。

Because there is no single function type in the JRE, there are also only very few methods which take a function type. This is another thing that makes first-class and anonymous functions hard to use in Java. Once you have one, there's not much you can do with it. You can't filter an array with a predicate function, you can't transform a list using a mapping function, you can't sort a gridview using a comparator function.

这也是为什么我对Lambda项目的某些迭代感到失望的原因之一。他们一直从项目中删除功能类型的介绍,尽管缺少功能类型是恕我直言,这是最大的问题之一。丑陋的语法可以用IDE技巧来解决,缺少标准函数类型就不能解决。 (更不用说所有使用JVM和JRE但不使用Java的人。他们将匿名内部SAM类的语法糖添加到Java语言中并没有从中受益,只是因为他们使用 Java语言。它们需要的是函数类型,以及一个使用函数类型的更新的集合库。)

This is also one of the reasons why I am so disappointed with some of the iterations of Project Lambda. They keep dropping the introduction of Function Types from the project, although the lack of function types is IMHO one of the biggest problems. The ugly syntax can be fixed with IDE trickery, the lack of standard function types can't. (Not to mention all those people who use the JVM and the JRE but don't use Java. They don't benefit one bit from adding syntactic sugar for anonymous inner SAM classes to the Java language, simply because they do not use the Java language. What they need are function types, and an updated collection library which uses function types.)

所以,我们现在最多有四个问题:

So, we're up to four problems now:


  1. 语法开销,因为您必须使用 f.apply(a) new Function1< A,A>(){public A apply(A a){return a; }} (顺便说一句,这就是身份功能,即绝对不做的功能,它占用58(!)个字符),

  2. 建模开销,因为除了您真正关心的域类型之外,您还必须自己定义自己的函数类型,

  3. 有限的用途,因为您已经创建了lambda,实际上没有很多方法将lambda作为参数和

  4. 有限重用,因为即使 if 您会发现需要一个lambda的方法,而不需要您的 lambda的方法,因为类型不匹配。

  1. syntactic overhead because you have to use stuff like f.apply(a) and new Function1<A, A>() { public A apply(A a) { return a; }} (BTW, that's the identity function, i.e. the function which does absolutely nothing, and it takes up 58(!) characters),
  2. modelling overhead because you have to define your own function types yourself, in addition to the domain types you actually care about,
  3. limited usefulness because once you have created your lambdas, there aren't actually that many methods which take lambdas as arguments and
  4. limited reuse because even if you find a method that takes a lambda, it doesn't take your lambda, since the types don't match up.

当我谈论建模开销时,我不仅在谈论一个 Function1 类型。输入原始类型…

And when I talk about "modelling overhead", I'm not just talking about one Function1 type. Enter primitive types …

您是否注意到我如何使用 Integer s而不是 int 在我上面的代码中?是的,没错,是编程语言历史上最大的设计失误,Java的Original Sin,是每位Java程序员存在的祸根,再次使我们陷入困境:原始类型。

Have you noticed how I used Integers and not ints in my code above? Yes, that's right, the single biggest design screwup in the history of programming languages, Java's Original Sin, the bane of every Java programmer's existence, has once again come to bite us in the ass: primitive types.

您会在Scala中看到一个完全 one 类,该类表示带有 n 个参数的函数。称为 FunctionN [T₁,T²,…,Tn,R] 。因此,对于没有任何参数的函数,只有一个类 Function0 [R] ,一个类 Function1 [T,R] 对于带有一个参数的函数,一类 Function3 [A,B,C,R] 对于带有三个参数的函数,依此类推,一直到20左右,我相信。

You see, in Scala, there is exactly one class that represents a function with n arguments. It's called FunctionN[T₁, T₂, …, Tn, R]. So, there is exactly one class Function0[R] for functions without any arguments, one class Function1[T, R] for functions with one argument, one class Function3[A, B, C, R] for functions with three arguments and so forth, all the way up to something around 20, I believe.

在C#中,恰好有两个类代表具有 n 自变量: Func Action 。这是因为没有代表 no type的类型。因此,您不能使用C#声​​明不返回任何内容的函数( void 是修饰符,而不是类型),因此需要单独的类型( Action )代表不返回任何内容的函数。因此,您有两个类 Func< R> Action ,它们表示不带任何参数的函数,再次,两个类 Func< T,R> Action< T> 分别表示一个参数的函数,依此类推。直到大约20。(在Scala中,不返回任何内容的函数仅具有返回类型 Unit ,因此您可以仅具有 Function2 [Int,Int,Unit] 。)

In C#, there are exactly two classes that represent a function with n arguments: Func<T₁, T₂, …, Tn, R> and Action<T₁, T₂, …, Tn>. This is because there is no type which represents "no type". So, you cannot declare a function which doesn't return anything using C# (void is a modifier, not a type), and thus you need a separate type (Action) to represent functions that don't return anything. So, you have two classes Func<R> and Action, which represent functions that don't take any arguments, two classes Func<T, R> and Action<T> which represent functions of one argument and so forth, again up to circa 20. (In Scala, a function that doesn't return anything simply has the return type Unit, so you can just have a Function2[Int, Int, Unit], for example.)

在Java中,您需要10× 9 n 类型代表n个参数的函数。让我用一个一个参数来证明这一点:

In Java, however, you need 10×9n types to represent a function of n arguments. Let me demonstrate that with just one argument:

interface Action1_T                 { void    apply(T       a); }
interface Action1_byte              { void    apply(byte    a); }
interface Action1_short             { void    apply(short   a); }
interface Action1_int               { void    apply(int     a); }
interface Action1_long              { void    apply(long    a); }
interface Action1_float             { void    apply(float   a); }
interface Action1_double            { void    apply(double  a); }
interface Action1_boolean           { void    apply(boolean a); }
interface Action1_char              { void    apply(char    a); }
interface Function1_T_R             { R       apply(T       a); }
interface Function1_T_byte          { byte    apply(T       a); }
interface Function1_T_short         { short   apply(T       a); }
interface Function1_T_int           { int     apply(T       a); }
interface Function1_T_long          { long    apply(T       a); }
interface Function1_T_float         { float   apply(T       a); }
interface Function1_T_double        { double  apply(T       a); }
interface Function1_T_boolean       { boolean apply(T       a); }
interface Function1_T_char          { char    apply(T       a); }
interface Function1_byte_R          { R       apply(byte    a); }
interface Function1_byte_byte       { byte    apply(byte    a); }
interface Function1_byte_short      { short   apply(byte    a); }
interface Function1_byte_int        { int     apply(byte    a); }
interface Function1_byte_long       { long    apply(byte    a); }
interface Function1_byte_float      { float   apply(byte    a); }
interface Function1_byte_double     { double  apply(byte    a); }
interface Function1_byte_boolean    { boolean apply(byte    a); }
interface Function1_byte_char       { char    apply(byte    a); }
interface Function1_short_R         { R       apply(short   a); }
interface Function1_short_byte      { byte    apply(short   a); }
interface Function1_short_short     { short   apply(short   a); }
interface Function1_short_int       { int     apply(short   a); }
interface Function1_short_long      { long    apply(short   a); }
interface Function1_short_float     { float   apply(short   a); }
interface Function1_short_double    { double  apply(short   a); }
interface Function1_short_boolean   { boolean apply(short   a); }
interface Function1_short_char      { char    apply(short   a); }
interface Function1_int_R           { R       apply(int     a); }
interface Function1_int_byte        { byte    apply(int     a); }
interface Function1_int_short       { short   apply(int     a); }
interface Function1_int_int         { int     apply(int     a); }
interface Function1_int_long        { long    apply(int     a); }
interface Function1_int_float       { float   apply(int     a); }
interface Function1_int_double      { double  apply(int     a); }
interface Function1_int_boolean     { boolean apply(int     a); }
interface Function1_int_char        { char    apply(int     a); }
interface Function1_long_R          { R       apply(long    a); }
interface Function1_long_byte       { byte    apply(long    a); }
interface Function1_long_short      { short   apply(long    a); }
interface Function1_long_int        { int     apply(long    a); }
interface Function1_long_long       { long    apply(long    a); }
interface Function1_long_float      { float   apply(long    a); }
interface Function1_long_double     { double  apply(long    a); }
interface Function1_long_boolean    { boolean apply(long    a); }
interface Function1_long_char       { char    apply(long    a); }
interface Function1_float_R         { R       apply(float   a); }
interface Function1_float_byte      { byte    apply(float   a); }
interface Function1_float_short     { short   apply(float   a); }
interface Function1_float_int       { int     apply(float   a); }
interface Function1_float_long      { long    apply(float   a); }
interface Function1_float_float     { float   apply(float   a); }
interface Function1_float_double    { double  apply(float   a); }
interface Function1_float_boolean   { boolean apply(float   a); }
interface Function1_float_char      { char    apply(float   a); }
interface Function1_double_R        { R       apply(double  a); }
interface Function1_double_byte     { byte    apply(double  a); }
interface Function1_double_short    { short   apply(double  a); }
interface Function1_double_int      { int     apply(double  a); }
interface Function1_double_long     { long    apply(double  a); }
interface Function1_double_float    { float   apply(double  a); }
interface Function1_double_double   { double  apply(double  a); }
interface Function1_double_boolean  { boolean apply(double  a); }
interface Function1_double_char     { char    apply(double  a); }
interface Function1_boolean_R       { R       apply(boolean a); }
interface Function1_boolean_byte    { byte    apply(boolean a); }
interface Function1_boolean_short   { short   apply(boolean a); }
interface Function1_boolean_int     { int     apply(boolean a); }
interface Function1_boolean_long    { long    apply(boolean a); }
interface Function1_boolean_float   { float   apply(boolean a); }
interface Function1_boolean_double  { double  apply(boolean a); }
interface Function1_boolean_boolean { boolean apply(boolean a); }
interface Function1_boolean_char    { char    apply(boolean a); }
interface Function1_char_R          { R       apply(char    a); }
interface Function1_char_byte       { byte    apply(char    a); }
interface Function1_char_short      { short   apply(char    a); }
interface Function1_char_int        { int     apply(char    a); }
interface Function1_char_long       { long    apply(char    a); }
interface Function1_char_float      { float   apply(char    a); }
interface Function1_char_double     { double  apply(char    a); }
interface Function1_char_boolean    { boolean apply(char    a); }
interface Function1_char_char       { char    apply(char    a); }

这是90(!)种不同的类型,仅表示需要一个参数的事物的概念。

That's 90(!) different types just to represent the concept of "something that takes one argument".

当然,如果我想写一些以函数作为参数的东西,那么我也需要相应数量的重载。要编写一个基于谓词过滤某些值的方法,我需要该函数的9个重载,这些重载使用 Function1_T_boolean Function1_byte_boolean Function1_short_boolean Function1_int_boolean Function1_long_boolean Function1_float_boolean Function1_double_boolean Function1_boolean_boolean Function1_char_boolean

And, of course, if I want to write something which takes a function as an argument, I need to have a corresponding number of overloads as well, so if I want to write a method which filters some values based on a predicate, I need 9 overloads of that function which take Function1_T_boolean, Function1_byte_boolean, Function1_short_boolean, Function1_int_boolean, Function1_long_boolean, Function1_float_boolean, Function1_double_boolean, Function1_boolean_boolean and Function1_char_boolean.

(顺便说一句:这仍然忽略检查的异常。从技术上讲,我们还需要2 n 个副本这90个接口中的每个接口,其中n是存在于其中的不同类型的检查异常的数量Java。)

(By the way: this still ignores checked exceptions. Technically, we also need 2n copies of every single one of those 90 interfaces, where n is the number of different types of checked exceptions that exist in Java.)

所以,这是数字5和6的原因:类型数量和方法数量急剧膨胀。

So, that's reasons number 5 and 6: a massive explosion in the number of types and correspondingly the number of methods.

如果将所有这些放在一起,那么我想您会同意Java中的匿名内部类比匿名函数要麻烦得多,嗯,几乎所有其他编程语言都如此。

If you put all of that together, then I guess you would agree that anonymous inner classes in Java are much more cumbersome than anonymous functions in, well, pretty much every other programming language.

但是还有更多!

我们甚至还没有提到闭包!尽管闭包与第一类和匿名函数正交,但它们还是匿名和第一类函数最重要(最有趣)的用例之一。内部类(无论是否匿名)都是闭包,但正如@Jon Skeet所指出的那样,它们受到严重限制。

We haven't even talked about closures yet! While closures are orthogonal to first-class and anonymous functions, they are also one of the most important (and interesting) use cases of anonymous and first-class functions. Inner classes (whether anonymous or not) are closures, but as @Jon Skeet already pointed out, they are severely limited.

总而言之,我会说不,Java不支持匿名函数。

In conclusion, I would say that No, Java does not support anonymous functions.

Java 8在语言中引入了 target-typed lambda文字,以及功能接口的概念和一些标准库功能接口类型,以及一个新的基于大量使用功能接口的流。 Java 9、10和11添加了更多的标准库功能接口类型和扩展的Streams。 Java 11添加了局部变量类型推断

Java 8 introduced target-typed lambda literals into the language, as well as the notion of a functional interface, and some standard library functional interface types, plus a new collection API based on Streams that makes heavy use of functional interfaces. Java 9, 10, and 11 added further standard library functional interface types and expanded Streams. Java 11 added local variable type inference.

那么, target-typed 是什么意思?这就是我上面提到的问题的答案:由于没有用于表示函数的标准化库类型,因此每个人都发明了自己的类型。因此,即使我想执行相同的逻辑,也必须为我使用的每个API编写不同的函数。

So, what does target-typed mean? It is the answer to the problem I talked about above: since there were no standardized library types for representing functions, everybody invented their own types. Therefore, even if I want to perform the same logic, I have to write a different functions for every API I am using.

如果他们只是简单地引入了一组新函数,函数类型,例如

If they had simply introduced a set of new function types, e.g.

interface Function1_int_int {
    int apply(int x)
}

并说lambda文字扩展为该函数类型的实例,如下所示:

And said that lambda literals expand to instantiations of that function type like this:

(int x) -> x + 1

等价于

new Function1_int_int {
    @Override public int apply(int x) {
        return x + 1;
    }
}

然后,您将无法在任何地方使用lambda ,因为没有现有的库接受 Function1_int_int 作为参数!您将不得不向每个现有的具有以一段代码作为参数概念的库中添加新的重载。

Then, you would not be able to use lambdas anywhere, since no existing library takes a Function1_int_int as an argument! You would have had to add new overloads to every existing library that has the concept of "taking a piece of code as an argument". This is simply not scalable.

他们所做的代替 是在Java中引入了一些结构类型化,类型的名称不相关,并且像这样的lambda:

What they did instead was to introduce a bit of structural typing into Java, where the names of the types are irrelevant, and a lambda like this:

(int x) -> x + 1

can be passed anywhere a type of the following form is expected:

can be passed anywhere a type of the following form is expected:

interface * {
    int *(int *)
}

or

class * {
    int *(int *) // abstract!
}

And it will automatically implement the class or interface with the correct name, and automatically implement the method with the correct name.

And it will automatically implement the class or interface with the correct name, and automatically implement the method with the correct name.

And not only is this based on structural typing, as opposed to the entire rest of Java’s type system which is nominal, it is also based on the context it is used in, and that is what is meant by \"target-typed\": the type of the lambda depends not on the lambda itself, but on what its target is, i.e. where it is used.

And not only is this based on structural typing, as opposed to the entire rest of Java's type system which is nominal, it is also based on the context it is used in, and that is what is meant by "target-typed": the type of the lambda depends not on the lambda itself, but on what its target is, i.e. where it is used.

Back to our example:

Back to our example:

static <A, B> List<B> morph(List<A> l, Function<A, B> f) {
    List<B> r = new ArrayList<>();
    for (var e: l) r.add(f.apply(e));

    return r;
}

The only significant difference to Java pre-8 here is that I am using the pre-defined java.util.function.Function<T, R> functional interface, which means I don’t have to write my own.

The only significant difference to Java pre-8 here is that I am using the pre-defined java.util.function.Function<T, R> functional interface, which means I don't have to write my own.

Here’s our list again (same code as before):

Here's our list again (same code as before):

List<Integer> l = Arrays.asList(1, 2, 3, 4, 5);

But here, how we call our morph, is the major difference. Instead of this:

But here, how we call our morph, is the major difference. Instead of this:

morph(l, new Function<Integer, Integer>() {
    @Override public Integer apply(Integer n) {
        return n + 1;
    }
});

We now have this:

We now have this:

morph(l, n -> n + 1);






Oh, I almost forgot: there was another question somewhere:


Oh, I almost forgot: there was another question somewhere:


What actually is an anonymous function

What actually is an anonymous function

That’s easy. A function with no name.

That's easy. A function with no name.


and how you can say that some language supports anonymous functions?

and how you can say that some language supports anonymous functions?

If it makes it easy to work with them.

If it makes it easy to work with them.

Java pre-8 doesn’t: there is a difference between supporting anonymous functions and being able to emulate a subset of the features of anonymous functions by encoding them into anonymous inner SAM classes. Java post-8 is much nicer.

Java pre-8 doesn't: there is a difference between supporting anonymous functions and being able to emulate a subset of the features of anonymous functions by encoding them into anonymous inner SAM classes. Java post-8 is much nicer.

这篇关于如何用lambda演算术语定义匿名函数(或如何说某些语言支持匿名函数)?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆