c3c icon indicating copy to clipboard operation
c3c copied to clipboard

Anonymous function proposal

Open lerno opened this issue 3 years ago • 12 comments

fn int test(int x)
{
    fn int square(int y)
    {
        return y * y;
    }
    return square(x) + square(x + 1);
}

lerno avatar Apr 10 '22 07:04 lerno

Feel free to vote on this one using 👍 and 👎

lerno avatar Apr 10 '22 07:04 lerno

Alternative syntactic forms:

fn int test(int x)
{
    const square = fn int(int y) { return y * y; }
    return square(x) + square(x + 1);
}

Which then would allow things like:

fn int test(int x)
{
    return do_something(fn int(int y) { return y * y; });
}

lerno avatar Apr 10 '22 09:04 lerno

Please note that these are not closures, so nested functions cannot access the variables of the enclosing function.

lerno avatar Apr 10 '22 19:04 lerno

Another open question is whether such anonymous functions could be declared anywhere in the scope of a function, or only at the top level:

fn void test()
{
    // Always ok
    fn void foo() { ... }
    for (int i = 0; i < 10; i++)
    {
        // Is this ok?
        fn void bar() { ... } 
    }
}

lerno avatar Apr 10 '22 21:04 lerno

       

// Is this ok?
fn void bar() { ... }

:point_down:

data-man avatar Apr 11 '22 05:04 data-man

And an anonymous macros too!

macro int test(int x)
{
    macro int square(int y)
    {
        return y * y;
    }
    return @square(x) + @square(x + 1);
}

data-man avatar Apr 12 '22 02:04 data-man

To be honest, I'm not particularly keen on adding macros. I have a hard time motivating the functions...

lerno avatar Apr 12 '22 10:04 lerno

In Nim language unit testing implemented as macros:

suite "description for this stuff":
  echo "suite setup: run once before the tests"
  
  setup:
    echo "run before each test"
  
  teardown:
    echo "run after each test"
  
  test "essential truths":
    # give up and stop if this fails
    require(true)
  
  test "slightly less obvious stuff":
    # print a nasty message and move on, skipping
    # the remainder of this block
    check(1 != 1)
    check("asd"[2] == 'd')
  
  test "out of bounds error is thrown on bad access":
    let v = @[1, 2, 3]  # you can do initialization here
    expect(IndexDefect):
      discard v[4]
  
  echo "suite teardown: run once after the tests"

I think this approach can be easily implemented in C3 using anonymous macros.

data-man avatar Apr 13 '22 10:04 data-man

I think this approach can be easily implemented in C3 using anonymous macros.

How?

lerno avatar Apr 15 '22 12:04 lerno

How?

Something like this:


module testing;
extern fn void printf(char*,...);

macro void unittest(#name; @body())
	macro void expect(#condition)
	{
	      if (#condition)
	      {
	         printf("Test [%s] passed\n", $stringify(#condition));
	      }
	      else
	      {
	         printf("Test [%s] failed\n", $stringify(#condition));
	      }
	  }

	macro void suite(#name; @body())
	{
		printf("Suite %s\n", $stringify(#name));
		@body();
	}
{
    printf("Testing %s\n", $stringify(#name));
    @body();
}

fn void main()
{
    @unittest(aaa,
    	@suite(comparisons,
		    @expect(1==1);
	        @expect(1!=1))
	);
}

For now, only expect(#condition) works in this PoC. :)

data-man avatar Apr 16 '22 09:04 data-man

Outstanding questions:

  1. What happens when a macro contains an anonymous function and it is inlined?
macro foo(int x)
{
   fn int square(int x)
   {
     return x * x;
   }
   return square(x) + x;
}

Now consider this use:

fn test(int a, int b)
{
     return foo(a) + foo(b);
}

I the macro is naively inlined, then we get two copies of "square" here. A better solution would perhaps be that the function is never duplicated.

  1. Continuing on (1) what about this:
macro foo(a)
{
   var $Type = $typeof(a);
   fn $Type square($Type a)
   {
      return a * a;
   }
}

If the above would be valid, then we're essentially creating generic functions by folding an inner function inside of a macro. While this might be useful, I think it's preferable to have something like:

macro foo(a) @noinline { ... }
// or
generic foo(a) { ... }
  • If one wants that functionality.
  1. Another possibility is obviously to not allow nested functions in macros at all.

  2. If nested functions are not allowed in macros, then the question if they are allowed in defers must also be considered.

lerno avatar Sep 30 '22 22:09 lerno

@data-man Note that this would not work:

    @unittest(aaa,
    	@suite(...)
    );

Because the @suite here would be evaluated in the calling scope, not in the macro's scope.

lerno avatar Oct 01 '22 13:10 lerno

foo = fn int(int y, double d) { return (int)(y * d); }; added.

lerno avatar Jan 25 '23 11:01 lerno