通过参数传递子程序/函数

时间:2010-12-08 21:17:39

标签: function programming-languages theory parameter-passing

我只是想知道允许函数通过参数传递的语言的优点是什么?希望有几个例子可以解决这个问题。

哪些流行的编程语言允许这样做?

3 个答案:

答案 0 :(得分:3)

假设我有一组对象,我想根据一些标准对它们进行排序。

如果我可以将函数传递给函数,那很简单:

collection.sort((a, b) => a.SomeProperty.compareTo(b.SomeProperty));

如果我不能,那么为了做这样的事情,每次我想根据不同的标准进行排序时,我基本上必须重新实现排序算法。

答案 1 :(得分:1)

优点是,您可以将函数参数化为不易表示为普通值的函数。

例如,如果要查找满足集合中某个谓词的项,或者想要查明集合中的所有项是否满足谓词,则谓词最自然地表示为元素类型中的函数布尔。

另一个非常常见的例子是排序函数,它将比较函数作为参数,因此集合的元素可以通过自定义排序键进行排序。

当然,在不允许函数作为参数但具有对象系统(在java中读取:)的语言中,您可以通过使用实现排序函数的特定接口的对象来解决此问题。 / p>

  

哪些流行的编程语言允许[将函数作为参数传递]?

C,C ++,C#,Objective C,Python,Ruby,Perl,PHP,Javascript,基本上所有功能或功能语言。

应该注意的是,与列表中的其他语言不同,C和C ++(在C ++ 0x之前)没有闭包。

答案 2 :(得分:1)

能够将函数传递给其他函数的优点是它允许您编写更清晰,更通用的代码。例如,考虑一个合理实际的函数,它将列表中的所有数字相加:

sum []     = 0
sum (x:xs) = x + sum xs

这是Haskell语法,您可能不熟悉;这两行定义了两种不同的情况,具体取决于输入是否为空列表,在这种情况下总和为零,或者x前置于列表xs,在这种情况下总和为{ {1}}加上x的总和。用更传统的语言(特别是任何一种语言),你都有

xs

现在,假设我们想要在列表中找到数字的乘积,而不是:

function sum(xs) {
  var total = 0
  for x in xs {
    total = x + total
  }
  return total
}

在传统语言中,它看起来更像是

product []     = 1
product (x:xs) = x * product xs

有趣的是,这两个功能看起来几乎相同。唯一的区别是function product(xs) { var total = 1 for x in xs { total = x * total } return total } 替换为01替换为+。实际上,事实证明我们可以概括*sum

+

这里,foldr f i [] = i foldr f i (x:xs) = f x (foldr f i xs) 有三个参数:一个双参数函数foldr,一个常量f和一个列表。如果列表为空,则返回常量(例如i);否则,我们将函数应用于(a)列表的第一个元素,以及(b)折叠列表其余部分的结果。在更传统的语言中,这看起来像

sum [] = 0

这意味着我们可以将function foldr(f,i,xs) var result = i for x in xs { result = f(x, result) } return result } sum简化为

product

(这里,sum = foldr (+) 0 product = foldr (*) 1 (+)是两个参数函数,分别对它们的参数进行加法和乘法。)如果没有第一类函数,就不能这样做。 (我正在做的另一件事是取消最后一个参数;这称为currying,并且相当方便。我的想法是,如果我不给它(*)所有的参数,它会返回一个函数,它是期待其余的人。但如果你觉得它令人困惑,想象一下定义说foldr。)

但是这里的事情可以变得更有趣。假设您有一个数字列表,并且您想要计算列表中每个数字的平方:

sum xs = foldr (+) 0 xs

但这显然是可抽象的:如果您想要否定列表中的每个元素,或者如果您有一个电子邮件列表并想要获取发件人,或者其他任何内容,那么几乎完全相同的代码就可以工作。所以,我们抽象:

squares []     = []
squares (x:xs) = (x^2) : squares xs

function squares(xs) {
  var result = []
  for x in xs {
    result = result ++ [x^2]
  }
  return result
}

但有趣的是,我们还可以根据之前的map f [] = [] map f (x:xs) = f x : map f xs function map(f,xs) { var result = [] for x in xs { result = result ++ [f(x)] } return result } squares = map (^2) # (^2) is the function which squares its argument. negations = map negate emailSenders = map getSender 实施map。首先,我们需要定义函数组合foldr

.

这表示f . g = \x -> f (g x) f的组合是一个参数的新匿名函数,它本身将g应用于gx结果。现在,我们可以定义f

map

这里,map f = foldr ((:) . f) [] 是一个获取元素和列表的函数,并返回该列表前面的元素。 (:)(:) . f相同,\x -> (:) (f x)(根据我提到的讨论规则)与\x xs -> f x : xs相同。换句话说,在折叠的每一步,将f x置于我们迄今为止所拥有的位置之前。 (这不会使我们与map相反,因为foldr“内外”工作原样。)

map的这个定义使用高阶函数.来构造它传递给foldr的函数。这么多函数作为参数传递!这让我们可以做像写

这样的事情
f &&& g = \x -> (f x, g x)

然后用它来写

sendersAndRecipients = map (getSender &&& getRecipient) . fetchEmails

通过将函数视为值,您可以获得很多力量。您可以编写诸如map&&&之类的通用过程,这些过程允许您稍后编写简洁但可读的代码。传递函数对于回调也很方便:sendMessage(theMessage,fn),其中fn是收到响应时要运行的函数。这只是一种非常自然的工作方式。

至于支持这种语言的语言:老实说,维基百科知道的比我想的要好。但我会试一试。 C和C ++可以这样做:你不能在线编写函数,但你可以传递函数指针。尽管如此,它在任何一种语言中都不是很常见(在C语言中更少)。任何OO语言都可以,如果你定义一个具有你想要的功能的单一方法的类,但这非常笨重。然而,这就是Java所做的。另一方面,C#实际上具有可以作为参数传递的函数。 Ruby(也有“块”,这是某些用例的特殊语法),Python,Perl和JavaScript都支持传递函数,就像每种函数式编程语言一样:Lisps(Scheme,Common Lisp,Clojure,... ); ML家族(SML,OCaml,......); Haskell(可能与ML家族混在一起);斯卡拉;和别的。这是一个很有用的功能,所以看到它如此广泛并不奇怪。