【问题标题】:What exactly can higher order functions in Haskell do that extended C can't?Haskell 中的高阶函数究竟能做什么扩展 C 不能做到的事情?
【发布时间】:2014-04-07 12:11:40
【问题描述】:

GCC 有 C extension 允许使用嵌套函数。

实际上,我不明白 Haskell(或其他纯函数式语言)中的高阶函数究竟能做什么,而 C(函数指针和嵌套函数扩展)却不能?

【问题讨论】:

  • 高阶函数与嵌套函数定义几乎无关。您可以在没有此扩展的情况下编写高阶函数;只需将函数指针作为参数或返回一个。
  • 是的。这些语言同样“强大”,haskell 只是可以做这些高阶功能的事情,而无需 C 所需的冗长。就像 C 可以处理状态而没有 haskell 需要的冗长。
  • @SteveCox 将{ 替换为do= 替换为<-; 替换为空格,} 替换为取消缩进?我不太确定这是否非常冗长。

标签: c haskell lambda functional-programming higher-order-functions


【解决方案1】:

我从未使用过这个 gcc 扩展;但我会尝试根据我对您提供的链接的解释进行解释。

就我对文档的理解而言,关键的区别在于这些嵌套函数不允许您构建函数式程序员在“闭包”下会理解的内容——这些嵌套函数不能在其定义上下文之外使用,因为它们使用的堆栈变量在外部函数退出后会丢失。

在 Haskell 中,当我执行以下操作时:

const x = \y -> x 
foo = const 2      -- remember or "close over" 2
bar = foo 1        -- now, bar == 2

您会看到,当将const 应用于2 时,参数2 被我们称为foo 的闭包“保存”了。之后,在bar 中,2 可以被调用并返回——因为它仍然在foo 中被记住。但是,在 C 中:

typedef int (*int_to_int_fn)(int);
int_to_int_fn constant(int x)
{
  int constant_impl(int y) { return x; }
  return constant_impl;
}

int main()
{
   int_to_int_fn foo = constant(2);
   int bar = foo(1);
}

这可能甚至无法编译,但如果编译,它将违背我们对 C 函数的基本期望:foo 中的 x,由 constant 的参数组成,需要留在某个地方(可能不在堆栈上!),直到一段时间后foo 被调用。这不是原始东西的工作方式——从这个意义上说,C 是原始的(我们可能需要在堆上分配一些东西、复制东西、稍后清理它、担心引用/值等)。


查看 C++11 的 lambda 语法可能会有所启发。在那里,constant_impl 可以写成这样:

auto constant_impl = [x](int y){ return x; }

[x] 部分正是我们告诉编译器“请记住 x!”的地方。

【讨论】:

  • 它可以编译,但是根据the GCC documentation如果你在包含函数退出后尝试通过它的地址调用嵌套函数,那么一切都会崩溃。
【解决方案2】:

实际使用高阶函数的关键是您需要 闭包:一个普通的旧 C 函数除了其参数之外,只能使用全局/静态数据。这意味着类似

GHCi> 过滤器 (>4) [7,4,3,6,87,5,4]
[7,6,87,5]

不能真正工作,除非你全局定义

bool largerThan4(int i) {return (i>4);}

这显然无法扩展。没有办法从程序中的其他地方“注入”数字 4。函数式语言将此类信息封装在闭包中。

现在,这个 GNU C 扩展在有限的意义上为您提供了闭包,我认为这与 C++11 defines for lambdas capturing by reference 的含义相同:本地函数可以引用其包含范围内的变量。比如说,这很有效,

typedef std::vector<int> IntArray;

IntArray filter (const IntArray& a, std::function<bool(int)> pred) {
  IntArray result;
  for(auto& i: a) if (pred(i)) result.push_back(i);
  return result;
}

int main() {
  std::vector<int> v{{7,4,3,6,87,5,4}};
  int minNumber = 4;
  for(auto i: filter(v, [&](int i){return i>minNumber;}))
    cout << i << endl;
  return 0;
}

(在没有范围循环等的 C 中,这当然会更麻烦。)这里,本地匿名函数仅在 minNumber 仍然是 main 的范围时使用,所以 filter 保持在调用那个谓词时,它总是可以指向那个位置。

但这是一个非常简单的场景。在函数式语言中,您通常会更普遍地使用高阶函数,包括诸如

  • 从另一个函数返回一个函数。这已经是词法闭包的问题了——你不能在局部变量离开作用域后引用它!因此,您需要将该变量复制/移出作为额外回报。
  • 在闭包中使用来自其他闭包本身的变量,您无法直接控制这些变量。闭包不能无限期地存在,它们需要在某个时候被擦除——但是编译器怎么知道它什么时候是安全的呢?函数式语言通常是垃圾回收的,这就解决了这个问题。
  • 以自身为参数的函数递归。这意味着可以有效地存在变量引用循环 ("tying the knot")。这通常意味着程序将挂起,除非您进行惰性评估。

【讨论】:

    猜你喜欢
    • 2019-02-15
    • 1970-01-01
    • 1970-01-01
    • 2011-11-30
    • 1970-01-01
    • 1970-01-01
    • 2015-01-26
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多