This is the 15th day of my participation in the August More Text Challenge. For details, see:August is more challenging

Anonymous functions and closures

Anonymous function lambda

You can define an anonymous function using lambda syntax:

## examples/lambda.av

let three = (lambda (x,y) -> x + y end)(1, 2);
println(three);
Copy the code

Lambda (x,y) -> x + y end defines an anonymous function that takes arguments x and y and returns the sum of the two.

The basic definition of an anonymous function is

Lambda (argument 1, argument 2...) -> Parameter body expression endCopy the code

After the anonymous function is defined, we directly pass in the arguments 1 and 2, and the result of adding the two is assigned to the variable three and printed, which is the result 3.

Anonymous functions can be assigned to a variable by:

## examples/lambda.av

let add = lambda (x,y) ->
  x + y 
end;

three = add(1, 2);
println(three);
Copy the code

We assign the anonymous function to the variable add, which is equivalent to defining an add function and calling add(1, 2), which also returns 3.

As of 5.2.4, anonymous functions can also be defined using fn syntax:

let add = fn(x, y) { x + y};

p(add(1, 2));
Copy the code

In fact, defining a function by fn in the previous section is essentially a similar step, defining an anonymous function with a lambda and then assigning to the variable specified by the function name.

Functions are First Class citizens

In AviatorScript, a function is also a type that can be passed as an argument, returned as a function, and so on.

## examples/function_first_class.av

fn square(x) {
  return x * 2;
}

let add = lambda(x, y, f) ->
  f(x) + f(y)
end;

let add_n = lambda(x) ->
  lambda(y) -> 
    x + y
  end
end;

println(type(square));
println(type(add));
println(type(add_n));
Copy the code

We define three functions, each with fn and lambda, and check their types with type:

function
function
function
Copy the code

Both are of type function.

We call x and y with the third argument f, which is also a function, and then add them together, so we can use the function as an argument:

let s = add(1, 2, square);
println(s);
Copy the code

We pass the square function as f to add, and the result is square(1) + square(2), which equals 6:

6
Copy the code

Add_n demonstrates a function as a return value. Add_n takes an argument x and returns an anonymous function:

 lambda(y) -> 
    x + y
  end
Copy the code

This anonymous function takes another argument, y. It adds x and y and returns:

let add_3 = add_n(3);
println(type(add_3));
println(add_3(1));
println(add_3(99));
println(add_3(' test'));
Copy the code

We assign the return value of add_n(3) to the variable add_3, whose type is function. Whenever add_3 is called, it will add 3 and return the result:

function
4
102
3 test
Copy the code

This example also demonstrates closures, which we’ll cover in the next section.

Closure Closure

AviatorsScript functions all support closure. Functions that capture the current context can be accessed even outside of the context. A classic example is to define a counter:

## examples/closure.av let counter = lambda() -> let c = 0; lambda() -> let result = c; c = c + 1; return result; end end; let c1 = counter(); let c2 = counter(); println("test c1..." ); for i in range(0, 10) { x = c1(); println(x); } println("test c2..." ); for i in range(0, 10) { x = c2(); println(x); }Copy the code

Counter is a function that first initializes a local variable c, and then returns another anonymous function that returns the value of C incrementing each time it is called.

Next we call counter twice, assign to two variables C1 and c2, 10 times each, and output:

test c1... 0 1 2 3 4 5 6 7 8 9 test c2... 0, 1, 2, 3, 4, 5, 6, 7, 8, 9Copy the code

It can be seen that the values of C1 and C2 are completely independent and are respectively used for self-increasing count without interfering with each other.

C is defined in the function counter. In theory, the local variable is “destroyed” after counter returns, but the anonymous result function “captures” the local variable c and continues to access it even after counter returns. This is called a closure, and C is called a free variable.

Closures simulate OOP

Closures can “save” state and can therefore be used to simulate OOP:

## examples/closure_oop.av

## a function to return a rectangle instance.
fn rectangle(x, y) {
  let r = seq.map("x", x, "y", y);
  
  r.area = lambda() -> 
    return r.x * r.y;
  end;
  r.circum = lambda() ->
    return 2 * (r.x + r.y);
  end;
  
  return r;
}

let s1 = rectangle(3, 4);

println("s1 info:");
println(s1.x);
println(s1.y);
println(s1.area());
println(s1.circum());

let s2 = rectangle(9, 10);

println("s2 info:");
println(s2.x);
println(s2.y);
println(s2.area());
println(s2.circum());
Copy the code

The Rectangle function returns a rectangle object with width and length definitions of x and y. It has two methods, area and circum, to calculate the area and perimeter of the rectangle, respectively. Here we use a map to simulate the object, put x and y into the map as keys, define two anonymous functions assigned to r.area and r.circum, which is equivalent to defining methods on the object, and return the resulting map. The two anonymous functions are closures that capture the free variable r.

Next we defined two rectangles, S1 and S2, and printed their basic information. You can see that calls like s1.x and s1.area() are close to being used in an object-oriented way. Thanks to closures, the information for the two objects is completely independent:

s1 info:
3
4
12
14
s2 info:
9
10
90
38
Copy the code

We can even modify the properties of an object:

s2.x = 100;
s2.y = 200;
println("s2 info after setting:");
println(s2.x);
println(s2.y);
println(s2.area());
println(s2.circum());
Copy the code

Output:

s2 info after setting:
100
200
20000
600
Copy the code