Question

GCC has C extension which allows using nested functions.

Actually, I don't understand what exactly can higher order functions in Haskell do (or other pure functional language) that C (function pointers and nested functions extension) can't?

Was it helpful?

Solution

I've never used this gcc extension; but I'll try to explain according to my interpretation of the link you gave.

As far as I understand the documentation, the crucial difference is that those nested functions don't allow you to build what a functional programmer would understand under "closure" -- those nested functions can't be used outside their definition context, because the stack variables they use get lost after the outside function exits.

In Haskell, when I do the following:

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

You see that when applying const to 2, the argument 2 gets "saved" by the closure which we have called foo. Afterwards, in bar, 2 can be recalled and returned -- because it still is remembered in foo. However, in 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);
}

That probably doesn't even compile, but if it would, it would go against basic expectations we have of a C function: the x in foo, made out of the argument of constant, would need to stay somewhere (probably not on the stack!), until some time later foo gets called. That's not how stuff primitively works -- and C is primitive, in this sense (we'd probably need to allocate something on the heap, copy stuff, clean it up later, worry about references/values, etc.).


Some enlightenment can probably be achieved by looking at the C++11 lambda syntax. There, constant_impl could be written somewhere along this:

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

The [x] part there is exactly the place where we tell the compiler "please, remember x for me!".

OTHER TIPS

A crucial thing for practical use of higher-order functions is that you want closures: a plain old C function can, apart from its parameters, only use global/static data. That means something like

GHCi> filter (>4) [7,4,3,6,87,5,4]
[7,6,87,5]

can't really work, except if you define globally

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

which obviously doesn't scale. There is no way to "inject" the number 4 from somewhere else in your program. Functional languages pack this kind of information in closures.

Now, this GNU C extension gives you closures in a limited sense, I think it's the same sense that C++11 defines for lambdas capturing by reference: a local function can refer to variables within its containing scope. That works reasonably well for, say,

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;
}

(In C without range-loops etc. that would have been more cumbersome of course.) Here, the local anonymous function is used only while minNumber remains is the scope of main, so while filter keeps on calling that predicate it can always just point to that spot.

But that's a very simple scenario. In a functional language, you will normally use higher-order-functions much more pervasively, including techniques such as

  • Returning a function from another function. That's already a problem with lexical closure – you can't reference to a local variable after it has left the scope! So you'll need to copy/move that variable out as an extra return.
  • Using variables in closures that come from other closures themselves, which you have no direct control over. The closures can't stay around indefinitely, they'll need to be erased at some time – but how can the compiler know when it's safe? Functional languages are normally garbage-collected, which solves this issue.
  • Recursion of a function with itself as an argument. This means there can effectively be loops of variable references ("tying the knot"). Which usually means the program will hang unless you have lazy evaluation.
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top