Functions within functions (lambdas)

Go To Last Post
32 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Note this thread started at https://www.avrfreaks.net/forum/... where a passing comment mentioned the possibility of using "functions within functions" in C. That triggered the following discussion...

 

Side note: I would strongly disrecommend gcc's "nested function" extension, I've seen horrible bugs caused by it, it's not well supported, and it's almost never a good design choice.

Last Edited: Tue. Jun 19, 2018 - 09:28 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

alank2 wrote:
Can you do a function within a function 

digitalDan wrote:
most/all other compilers don't.

It is specifically not allowed by the 'C' standard.

 

EDIT

 

Originally a reply to: https://www.avrfreaks.net/commen...

Top Tips:

  1. How to properly post source code - see: https://www.avrfreaks.net/comment... - also how to properly include images/pictures
  2. "Garbage" characters on a serial terminal are (almost?) invariably due to wrong baud rate - see: https://learn.sparkfun.com/tutorials/serial-communication
  3. Wrong baud rate is usually due to not running at the speed you thought; check by blinking a LED to see if you get the speed you expected
  4. Difference between a crystal, and a crystal oscillatorhttps://www.avrfreaks.net/comment...
  5. When your question is resolved, mark the solution: https://www.avrfreaks.net/comment...
  6. Beginner's "Getting Started" tips: https://www.avrfreaks.net/comment...
Last Edited: Tue. Jun 19, 2018 - 09:44 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Just to warn you that you are discussing something from September 2017. The only relevant (recent) post is #55.

 

(oh and if one was contemplating a repeated sequence (ie "function") to be used several times within some other function wouldn't you just put the sequence into a separate "static inline" ?)

Last Edited: Tue. Jun 19, 2018 - 08:26 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Well, since we are discussing this anyway, I'd just like to add that C++ (since C++11) does have nested functions that for some reason are called "lambda expressions".

 

Here is an example (that you can copy and run here: https://www.onlinegdb.com/online...):

 

#include <stdio.h>

int main() {
	auto nested = [] (int i) {
		printf("Count is %d. Press a key to continue.", i);
		getchar();
	};
	for (int count = 0;;count++) {
		nested(count);
	}
}

 

These nested functions are defined in a somewhat weird way, but they come in handy sometimes.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

clawson wrote:
you are discussing something from September 2017

Indeed - but it does seem to be the OP reviving his own thread.

 

El Tangas wrote:
C++ (since C++11) does have nested functions that for some reason are called "lambda expressions".

Hmm ... I think  "lambda expressions" are somewhat different from conventional "functions" ?

 

And C++ is a different language from 'C'

Top Tips:

  1. How to properly post source code - see: https://www.avrfreaks.net/comment... - also how to properly include images/pictures
  2. "Garbage" characters on a serial terminal are (almost?) invariably due to wrong baud rate - see: https://learn.sparkfun.com/tutorials/serial-communication
  3. Wrong baud rate is usually due to not running at the speed you thought; check by blinking a LED to see if you get the speed you expected
  4. Difference between a crystal, and a crystal oscillatorhttps://www.avrfreaks.net/comment...
  5. When your question is resolved, mark the solution: https://www.avrfreaks.net/comment...
  6. Beginner's "Getting Started" tips: https://www.avrfreaks.net/comment...
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

El Tangas wrote:
for some reason are called
StackOverflow (of course!) has this:

 

https://stackoverflow.com/questi...

 

So it's from Lambda Calculus. I don't know the exact chronology of the thing but I suspect C++11 has "borrowed" the idea from Python where Lambda's were they are used extensively.

 

EDIT: seems I am quite wrong - Lambda's are pervasive across a whole raft of languages: https://en.wikipedia.org/wiki/An...

 

But I fear this thread is going off topic so all be warned that I will split this into a separate "functions within functions" in the compiler/programming forum shortly.

Last Edited: Tue. Jun 19, 2018 - 09:09 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

awneil wrote:
Hmm ... I think "lambda expressions" are somewhat different from conventional "functions" ? And C++ is a different language from 'C'

 

Yes, they are somewhat different, but in many cases you can use them as nested functions. And they are standard (in C++, not C of course).

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

clawson wrote:
split this into a separate "functions within functions" in the compiler/programming forum shortly

+1

 

wink

Top Tips:

  1. How to properly post source code - see: https://www.avrfreaks.net/comment... - also how to properly include images/pictures
  2. "Garbage" characters on a serial terminal are (almost?) invariably due to wrong baud rate - see: https://learn.sparkfun.com/tutorials/serial-communication
  3. Wrong baud rate is usually due to not running at the speed you thought; check by blinking a LED to see if you get the speed you expected
  4. Difference between a crystal, and a crystal oscillatorhttps://www.avrfreaks.net/comment...
  5. When your question is resolved, mark the solution: https://www.avrfreaks.net/comment...
  6. Beginner's "Getting Started" tips: https://www.avrfreaks.net/comment...
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

clawson, editing a post by the_real_seebs, wrote:
Note this thread started at https://www.avrfreaks.net/forum/... where a passing comment mentioned the possibility of using "functions within functions" in C

Specifically, this post: https://www.avrfreaks.net/commen...

 

 

Top Tips:

  1. How to properly post source code - see: https://www.avrfreaks.net/comment... - also how to properly include images/pictures
  2. "Garbage" characters on a serial terminal are (almost?) invariably due to wrong baud rate - see: https://learn.sparkfun.com/tutorials/serial-communication
  3. Wrong baud rate is usually due to not running at the speed you thought; check by blinking a LED to see if you get the speed you expected
  4. Difference between a crystal, and a crystal oscillatorhttps://www.avrfreaks.net/comment...
  5. When your question is resolved, mark the solution: https://www.avrfreaks.net/comment...
  6. Beginner's "Getting Started" tips: https://www.avrfreaks.net/comment...
Last Edited: Tue. Jun 19, 2018 - 09:43 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

As I suggested above if you find yourself doing something like:

void a_func(void) {
    switch(something) {
        case 0: {
            PORTB |= (1 << 3);
            x = 3;
            count++;
            break;
        }
        case 1: {
            PORTB |= (1 << 5);
            x = 5;
            count++;
            break;
        }
        case 2: {
            PORTB |= (1 << 1);
            x = 1;
            count++;
            break;
        }
        default: break;
    }
}

where you are repeating almost identical code but you don't want the overhead of a CALL/RET to a separate function then surely you'd just:

static inline void stuff(int n) {
    x = n;
    count++;
}

void a_func(void) {
    switch(something) {
        case 0: {
            PORTB |= (1 << 3);
            stuff(3);
            break;
        }
        case 1: {
            PORTB |= (1 << 5);
            stuff(5);
            break;
        }
        case 2: {
            PORTB |= (1 << 1);
            stuff(1);
            break;
        }
        default: break;
    }
}

That will just inline the "stuff" repeatedly but saves you retyping great swathes of repeated code. Also (more importantly) it gives you just one copy where all the bug fixes can be applied.

Last Edited: Tue. Jun 19, 2018 - 10:00 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

clawson wrote:
Also (more importantly) it gives you just one copy where all the bug fixes can be applied.

+10

 

Indeed - the optimiser may well "collect" the repeated code, but won't help with the maintenance ...

Top Tips:

  1. How to properly post source code - see: https://www.avrfreaks.net/comment... - also how to properly include images/pictures
  2. "Garbage" characters on a serial terminal are (almost?) invariably due to wrong baud rate - see: https://learn.sparkfun.com/tutorials/serial-communication
  3. Wrong baud rate is usually due to not running at the speed you thought; check by blinking a LED to see if you get the speed you expected
  4. Difference between a crystal, and a crystal oscillatorhttps://www.avrfreaks.net/comment...
  5. When your question is resolved, mark the solution: https://www.avrfreaks.net/comment...
  6. Beginner's "Getting Started" tips: https://www.avrfreaks.net/comment...
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I've had a number of times where I've had repeated code that would have been better served by a function within a function.  Usually it is repeated instructions that I'd prefer to optimize that need access to the local variables of the parent function.  Sometimes they end up in a function and I pass variables to them, sometimes some of those variables end up being globals, sometimes I just leave it repeated code.  I like the macro idea to at least not have multiple copies of code to correct/change, I'll try that next time.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1

alank2 wrote:
I like the macro idea to at least not have multiple copies of code to correct/change, I'll try that next time.
Don't use macros. They have no type checking. As I say, use a static inline C function. While my example in #10 could have replaced:

static inline void stuff(int n) {
    x = n;
    count++;
}

with

#define stuff(n) { \
    x = n; \
    count++; \
}

but the fact is that there's no type checking on this. 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

For those who write code in javascript use lambadas a lot. Since callbacks are used a lot, you just pass the lambada as the function. I think Lisp was one of the early languages to use lambadas, it has been around for some time- nearly as long as me!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

clawson wrote:
there's no type checking on this. 

and no scoping, and various other pitfalls ...

Top Tips:

  1. How to properly post source code - see: https://www.avrfreaks.net/comment... - also how to properly include images/pictures
  2. "Garbage" characters on a serial terminal are (almost?) invariably due to wrong baud rate - see: https://learn.sparkfun.com/tutorials/serial-communication
  3. Wrong baud rate is usually due to not running at the speed you thought; check by blinking a LED to see if you get the speed you expected
  4. Difference between a crystal, and a crystal oscillatorhttps://www.avrfreaks.net/comment...
  5. When your question is resolved, mark the solution: https://www.avrfreaks.net/comment...
  6. Beginner's "Getting Started" tips: https://www.avrfreaks.net/comment...
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I wasn't thinking about using the parameter/variable replacement features of a macro, just replacement.  I was actually thinking of something more like:

 

  instruction1;

  instruction2;

  instruction3;


  morecode;


  instruction1;

  instruction2;

  instruction3;

 

with

 

#define codereplacement \

  instruction1;\

  instruction2;\

  instruction3


  codereplacement;


  morecode;


  codereplacement;

 

Last Edited: Tue. Jun 19, 2018 - 01:21 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

And what I am saying to you is that a "better" way to do that in C is:

static inline typeout codereplacement(typein var) {
    instruction1;
    instruction2;
    instruction3;
}

My typein/typeout can be "void" if you like if there's nothing to be passed into or out of this sequence.

 

Pre-pro macros are the spawn of the devil.

 

MISRA has pretty strong views about this. Rule 16-2-1 says "The pre-processor shall only be used for file inclusion and include guards." That is the only use of the pre-processor they allow.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I got you clawson.  They discourage defines like #define MAX_ITEMS 1000 then as well?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I expect they would recommend an enum in 'C', or const in C++

Top Tips:

  1. How to properly post source code - see: https://www.avrfreaks.net/comment... - also how to properly include images/pictures
  2. "Garbage" characters on a serial terminal are (almost?) invariably due to wrong baud rate - see: https://learn.sparkfun.com/tutorials/serial-communication
  3. Wrong baud rate is usually due to not running at the speed you thought; check by blinking a LED to see if you get the speed you expected
  4. Difference between a crystal, and a crystal oscillatorhttps://www.avrfreaks.net/comment...
  5. When your question is resolved, mark the solution: https://www.avrfreaks.net/comment...
  6. Beginner's "Getting Started" tips: https://www.avrfreaks.net/comment...
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

alank2 wrote:
They discourage defines like #define MAX_ITEMS 1000 then as well?
They don't "discourage". They mandate that it simply cannot happen. Again this if reasons of type safety. What they would propose is:

const uint16_t max_items = 1000;

Any reasonable compiler will simply optimise the use of this. So that when you later write:

if (entered_num < max_items) {

there won't be a "max_items" actually created in memory and that then has a runtime read access in this statement. Instead the compiler will optimize this as if you had simply written:

if (entered_num < 1000) {

(just as it would have done if #define were used). But the difference is that this particular 1000 has a known type (uint16_t) so if entered_num were a uint32_t (say) then expect a warning from the compiler (certainly from a code analyser like lint, splint, cppcheck, Klockwork, QAC, etc etc) to point out that you are comparing a 32 bit wide to a 16 bit wide and this may not be what you intended. Such knowledge could not be made available to the compiler if just 1000 or MAX_ITEMS (as a #define of 1000) were used.

 

It's also a good idea to have the code compiled as C++ rather than C as C++ is much stringent about mixing of types and will call out things that C ignores.

 

When one first starts to work with MISRA it looks like a lot of really annoying nonsense. But greater minds than ours have contemplated all the things you can do in C (and C++) and the kind of things that often lead to problems (like mixing types) and have mandated a set of rules that makes sure you cannot do most of those things. So this makes for higher quality, safer code in the long run even if it does look like an uphill struggle to implement their directives.

 

The best idea is to learn MISRA then only write code that adheres to it in the first place. Sadly what happens in the real world is that large teams of programmers from all kinds of backgrounds collaborate on some code for a project using all kinds of approaches and implementations and then this is fed into a code analyser and some other team then spend week/months taking 10's of 1,000's of MISRA warnings and trying "solve" how to reimplement the same thing to be MISRA safe! (been there, done that, worn the T-shirt!)

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Indeed that is superb - thanks for the lesson clawson!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

BTW I notice some programmers use all upper case even when they use:

const uint16_t MAX_ITEMS = 1000;

For me that is a bit of a two edged sword. Sure when you later read:

if (entered_num < MAX_ITEMS) {

you are likely to read this as "MAX_ITEMS is clearly defined as an unchaging (const) value somewhere" but this kind of grates with the usual rule "only use all upper case for pre-pro macros". Of course if you live in a MISRA world where prepro can ONLY be use for header guards then I suppose it's OK to use upper case like this as it must be const.

 

BTW I forgot to say that you would usually want to limit the name scope of the const variable whatever you choose to name it. Othewise if you did something like:

const uint16_t MAX_ITEMS = 1000;

int main(void) {
    if (entered_num < MAX_ITEMS) {
        ...
    }
}

then while the intention is that nothing is really allocated in memory to create "MAX_ITEMS" the fact is that define in this way it is a global and the compiler cannot know whether another compilation unit might make an "extern" reference to this thing so it may be tempted to actually allocate storage. So in this case you would use:

static const uint16_t MAX_ITEMS = 1000;

int main(void) {
    if (entered_num < MAX_ITEMS) {
        ...
    }
}

so the compiler knows (in this case) that usage is limited to this file. When it sees no other access the optimiser will know it's safe to only generate a hard coded 1000 in the code that implements the if.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

A fundamental problem with using  static linline functions instead of macros

is that the functions often have to be rather far away from their uses.

Admittedly that is true of functions in general,

but functions in general are not used to replace

a few consecutive instances of repetitive code.

 

C++ once had a similar problem with template instantiation:

Local classes could not be used as template arguments.

 

MISRA rules do prohibit some good things.

Xmacros come to mind as an excellent way to avoid repeating oneself.

Anything, even xmacros can be done badly..

MISRA had to decide what they could allow

and still disallow enough of the bad stuff.

We have their decision.

 

"Demons after money.
Whatever happened to the still beating heart of a virgin?
No one has any standards anymore." -- Giles

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I am confused -

 

Dpes C disallow function calls from within functions? That is what it sounds like, above. Yet, this is something we do all the time:

 

void TopFunction(void) {
    FunctionOne();
    FUnctionTwo();
    FunctionThree();
}

What am I missing?

 

Thanks

Jim

Jim Wagner Oregon Research Electronics, Consulting Div. Tangent, OR, USA http://www.orelectronics.net

Last Edited: Tue. Jun 19, 2018 - 03:36 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

skeeve wrote:
is that the functions often have to be rather far away from their uses.
In this day and age, with a decent editor that need not really be a problem any more. A decent editor will likely "pop - up" the implementation of the inlined code if you hover at the point of invocation. Other functionality likely makes a switch between the two an easy task too.

 

Also in this sense it's not really better/worse than pre-pro macros is it? If anything pre-pro macros have a nasty habit of living in .h files which probably puts them even further distant from the invocation than somewhere in the same file.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

ka7ehk wrote:
Dpes C disallow function calls from within functions?
No Jim (how often have frustrated Trekkies wanted to say that phrase?) the thing we're talking about is something more like this:

static inline void stuff(int n) {
    x = n;
    count++;
}

void a_func(void) {
    switch(something) {
        case 0: {
            PORTB |= (1 << 3);
            stuff(3);
            break;
        }
        case 1: {
            PORTB |= (1 << 5);
            stuff(5);
            break;
        }
        case 2: {
            PORTB |= (1 << 1);
            stuff(1);
            break;
        }
        default: break;
    }
}

actually implemented as:

void a_func(void) {
    void stuff(int n) {
        x = n;
        count++;
    }

    switch(something) {
        case 0: {
            PORTB |= (1 << 3);
            stuff(3);
            break;
        }
        case 1: {
            PORTB |= (1 << 5);
            stuff(5);
            break;
        }
        case 2: {
            PORTB |= (1 << 1);
            stuff(1);
            break;
        }
        default: break;
    }
}

In this stuff() is a not just a function called from a function (we all do that all the time) but a function defined WITHIN a function. Andy pointed out previously that C does not allow this but...

 

https://gcc.gnu.org/onlinedocs/g...

 

So GCC has a special "extension" which you can access using -std=gnu99 rather than -std=c99 (and other gnu versus c options) that actually permits functions in functions.

 

As noted above C++11 expanded in 2011 to now add "lambda"s to C++ which is a more formal (and standard approved) way to define short functions (usually) within functions.

 

EDIT1: To be honest I cannot really see the appeal of any of this. I suppose it's about "scope" and locality when reading the source? My stuff() function is clearly something "private and personal" that is only involved in the implementation of a_func() so it's brought "inside" but for me it just confuses the legibility of a_func() by living there at the top.

 

EDIT2: Actually when you read that GCC manual page about their extension and it cites this as an example:

foo (double a, double b)
{
  double square (double z) { return z * z; }

  return square (a) + square (b);
}

notice the fairly nasty thing they have done here? To try and say "we don't want to overcomplicate foo() by having a big old function defined within it" they have crammed the entire function definition of square() onto one line. Not sure how that is supposed to help legibility? I would have written:

foo (double a, double b)
{
  double square (double z)
  {
      return z * z;
  }

  return square (a) + square (b);
}

which begins to look unwieldy. Not sure how this is really any "better" than simply:

static inline double square (double z)
{
  return z * z;
}

foo (double a, double b)
{
  return square (a) + square (b);
}

Sometimes you wonder if all these language extensions really help?!?

 

On that subject I read a brilliant interview with Bjarne Stroustrup yesterday:

 

https://www.theregister.co.uk/2018/06/18/bjarne_stroustrup_c_plus_plus/

 

In which he appears to be saying "a committee of 160 people reinventing MY language are creating a language that looks like it was designed by a committee"!

Last Edited: Tue. Jun 19, 2018 - 03:55 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Of course C  & most languages allow using functions in a nested fashion, where they have been defined elsewhere:

Not sure if any languages don't allow this

apples= my_func(your_func(bills_func(freds_func(volts))));

 

When in the dark remember-the future looks brighter than ever.   I look forward to being able to predict the future!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Is there some perceived benefit to having a function defined inside a function? Or, is it just "syntactic candy"?

 

Thanks

Jim

Jim Wagner Oregon Research Electronics, Consulting Div. Tangent, OR, USA http://www.orelectronics.net

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

clawson wrote:
skeeve wrote:
is that the functions often have to be rather far away from their uses.
In this day and age, with a decent editor that need not really be a problem any more. A decent editor will likely "pop - up" the implementation of the inlined code if you hover at the point of invocation. Other functionality likely makes a switch between the two an easy task too.
Not so loosely translated: It's not evil because a good editor can help you live with it.
Quote:
Also in this sense it's not really better/worse than pre-pro macros is it? If anything pre-pro macros have a nasty habit of living in .h files which probably puts them even further distant from the invocation than somewhere in the same file.
Oranges and grapefruit.

The macros under discussion would be defined in .c  files, used a few times in sight of their definitions, possibly undefined and never mentioned again.

They would not live in .h files.

"Demons after money.
Whatever happened to the still beating heart of a virgin?
No one has any standards anymore." -- Giles

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

ka7ehk wrote:

Is there some perceived benefit to having a function defined inside a function? Or, is it just "syntactic candy"?

 

For me it would have been so that it had access to the local variables of that function.  Sure you can pass them back and forth, but that seems wasteful.  It always seems to make code larger anyway.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

well, it existed in (Turbo?)Pascal long ago.

suppose you have two norms  an Euclidian one and a Manhattan like one, which is used only in a function

int norm (int x, int y) {

 return (sqrt(x*x + y*y));

}

void abritrary_function() {

int norm(int x, int y){

  return (abs(x) + abs(y));

}

 

....

norm(truc,troc); // one gets the cityblock norm

....

} // end of arbitrary function

may be comfortable

 

 

but "static inline" can be enough, if one can live with a lot of files .... (each file can countain a function with the same name and it is used "only" in the file, if I did understand)

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

The advantage is the access to the local variables. Consider, for instance, if you have a function somewhere else that takes a function pointer. If you hand them the address of a nested function, the function they call through that pointer has access to your local variables. This is occasionally desireable, and can allow improved thread-safety. But it's also incredibly bug-prone in GNU C, and not present at all in standard C.