Callers, safe replacement for function overloads and syntax simplification

Callers can accept one set of parameters to call a function, using their parameters to get the parameters of the function.

Must be defined with the keyword call, followed by the name, then a list of parameters, then ":' and the function call:

fn foo(a: i32) -> i32 { a + 1 }
call bar(a: i32, b: i32) : foo(a + b);

It is questionable whether it is necessary to prescribe a return value for the caller, but if it is, the syntax should be like this:

call bar(a: i32, b: i32) -> i32 : foo(a + b);

In addition, another alterative syntax for callers was invented, where the callers would be spelled out immediately after the function. If this syntax will be present, it is questionable whether we should leave it alone and when should we write the return value:

fn foo(a: i32) -> i32 
call bar(a: i32, b: i32) : foo(a + b)
{ a + 1 }

Callers can have the same name as the function being called, but with certain restrictions:

  • A caller cannot have as many parameters as the function being called or as many parameters as already defined callers for that function with the same name.
  • Callers cannot be created for template functions

Example:

fn foo(a: i32) -> i32 { a + 1 }
call foo(a: i32, b: i32) : foo(a + b);

All default parameters of a function must be expanded to an appropriate number of corresponding callers:

fn foo(a: i32, b: i32 = 0) -> i32 { a + b }

//At the compile stage it turns into :

fn foo(a: i32, b: i32) -> i32 { a + b }
call foo(a: i32) : foo(a, 0);

Calling a caller is not different from calling a function.

This substitution of function overloads is completely explicit. Any call is unambiguous and defined by the function/caller name + number of arguments. The same name still always calls the same code, only in a fraction of the cases with changes in the arguments. The use of callers should be allowed wherever it is possible to write functions.

Using callers with a name other than function allows to reduce the size of the code. And using callers with the same name as a function allows you to use them in the same situations where function overloading is useful.

A real example:

fn harmonic_mean(vec: Vec<f64>) -> f64 {
	let mut sum = 0.;
	for x in vec {
		sum += 1/x;
	}
	vec.len() as f64 / sum
}

call harmonic_mean(a: f64, b: f64) : harmonic_mean(vec![a, b]);

Examples of Alternative Syntax:

fn foo(a: i32, b: i32 = 0) -> i32 { a + b }

call foo(a: i32) : foo(a, 0);
//...
call foo(a: i32) -> i32 : foo(a, 0);

//If the possibility of other names will be absent: 
call foo(a: i32) : (a, 0);

//If they can be written only after the function signature:
fn foo(a: i32) -> i32 
call bar(a: i32, b: i32) : (a + b)
{ a + 1 }
//...
fn foo(a: i32) -> i32 
call bar(a: i32, b: i32) : (a + b),
call baz(a: i32, b: i32, c: i32) : (a + b + c)
{ a + 1 }
//...
fn foo(a: i32) -> i32 
call bar(a: i32, b: i32) : (a + b), baz(a: i32, b: i32, c: i32) : (a + b + c)
{ a + 1 }

//If the possibility of other names will be absent and if they 
//can be written only after the function signature:
fn foo(a: i32) -> i32 
call(a: i32, b: i32) : (a + b)
{ a + 1 }
//...
fn foo(a: i32) -> i32 
call(a: i32, b: i32) : (a + b), call(a: i32, b: i32, c: i32) : (a + b + c)
{ a + 1 }
//...
fn foo(a: i32) -> i32 
call(a: i32, b: i32) : (a + b), (a: i32, b: i32, c: i32) : (a + b + c)
{ a + 1 }

What does this solve, or where would this be useful? It's not clear (to me at least) where or how this would be used. Can you give a concrete example? Also:

fn foo(a: i32) -> i32 { a + 1 }
call bar(a: i32, b: i32) : foo(a + b);

How is this any different from

fn foo(a: i32) -> i32 { a + 1 }
fn bar(a: i32, b: i32) -> i32 { foo(a + b) }
7 Likes

Callers with other names are almost useless; they can only be handy because:

  • You may not have to specify the type of return value for them, and this may be handy if the return value is large.
  • They are easier to optimize because they are guaranteed to be a single function call.

It may even be worth removing this feature altogether and simplifying the syntax.

Generally, callers with the same name as a function are useful, they are a safe substitute for function overloading, but ensure that the same code is called. For example:

fn harmonic_mean(vec: Vec<f64>) -> f64 {
	let mut sum = 0.;
	for x in vec {
		sum += 1/x;
	}
	vec.len() as f64 / sum
}

call harmonic_mean(a: f64, b: f64) : harmonic_mean(vec![a, b]);

Or in the extreme case of simplifying syntax even:

fn harmonic_mean(vec: Vec<f64>) -> f64 
call(a: f64, b: f64) : (vec![a, b])
{
	let mut sum = 0.;
	for x in vec {
		sum += 1/x;
	}
	vec.len() as f64 / sum
}