tech

[English] [Japanese]

10.2. Multi-value return values

LuneScript supports multi-valued function return values.

For example, a function can return multiple values like this:

// @lnsFront: ok
fn func(): int,int {
   return 1, 2;
}
print( func() );  // 1 2

In the example above, func() returns 1 and 2. This multi-valued return value is passed as is as an argument to print().

Multi-valued return values are very useful, but there are still very few languages with multi-valued return values. Also, the second and subsequent values of the return value often have optional meanings.

So it's easy to forget the existence of the second and subsequent return values.

This can cause problems.

Bug due to multi-valued return value

The following code is Lua code, not LuneScript.

local function func1( txt ) 
   print( string.byte( txt:gsub( "b", "B" ) ) )
end
local function func2( txt ) 
   print( string.byte( (txt:gsub( "b", "B" )) ) )
end
func1( "abcb" ) -- 66
func2( "abcb" ) -- 97

This func1() and func2() give different results even though they are given the same argument "abcb". Do you know why?

By the way, txt:gsub( "b", "B" ) is a Lua standard library that returns a string that converts "b" in the string txt to "B", and string.byte() is a standard library that returns the value contained in the specified string.

That's the answer.

func1() passes the return value of txt:gsub( "b", "B" ) to string.byte as it is, while func2() passes (txt:gsub( "b", "B" )) enclosed in ().

As a result, func1() passes the multivalued value of txt:gsub( "b", "B" ) to string.byte() as is, while func2() passes only the string that is the first value of the multivalued txt:gsub( "b", "B" ) to string.byte().

string.byte() returns the value of the character at the index specified by the second argument, if passed the second argument. On the other hand, if the second argument is omitted, it returns the value of the first character.

And txt:gsub( "b", "B" ) is a function that returns many values. Specifically, it returns the converted string str and the number of places converted int.

Anyone using Lua should be aware that txt:gsub( "b", "B" ) is a function that returns a converted string. On the other hand, how many people are aware that this function returns multiple values?

And even if they did, how many of them would immediately realize that it would lead to failures?

Although we originally wanted to use only the string after conversion of txt:gsub( "b", "B" ) as an argument, the function behavior may change due to the unintentionally passed multi-valued return value.

In other words, when () should be used and only the beginning of multiple values should be used like func2(), multiple values may be used as they are like func1().

Remember what I wrote at the beginning here.

  • Multi-valued return values are very useful, but there are still very few languages with multi-valued return values.
  • Also, the second and subsequent values of the return value often have optional meanings.
  • So it's easy to forget the existence of the second and subsequent return values.

Multi-valued return values are convenient, but there are risks like this.

For LuneScript

In LuneScript, we decided to be explicit when dealing with multi-valued return values to avoid problems like this. In addition, if it is not specified, it will be a warning.

However, it is troublesome to specify when using multiple values.

Therefore, the following cases are limited as the cases that require clarification.

The destination of the multi-valued return value is

  • is a function argument and the argument is optional
  • If the value specified in return and that value can be omitted

In other words, when directly assigning a value to a variable such as let , explicitness is unnecessary. This is because I thought that the fact that we purposely prepared a variable to assign a value to would be the same as explicitly stating that we would use the second and subsequent multi-valued arguments.

How to express

The specific method of manifestation is as follows.

// @lnsFront: ok
fn func1(): int,int {
   return 1, 2;
}
fn func2( val1:int, val2:int!) {
   print( val1, val2 );
}

func2( func1()** );

By adding ** after the function call, like func1()** above, we indicate that we are using multiple values for that function call.

Specifications when using multi-value return values

As shown above, the result of executing a function that returns multiple values can be passed as an argument to another function.

However, the return value of a function that returns multiple values can be handled as it is only if nothing is specified after the function.

What this means is that when outputting the execution result of a function func1 that returns a multi-valued return value with print as shown below, if it is specified after func1, the execution result of func1 will only have the first value processed.

// @lnsFront: ok
fn func1(): int,int {
   return 1, 2;
}
print( func1() );      // 1 2
print( func1(), 10 );  // 1 10
print( 0, func1() );  // 0 1 2

For the example above,

  • For print( func1() ), the multi-valued return value 1 2 is processed as is.
  • For print( func1(), 10 ), 10 at the beginning of the multi-valued return value is processed.
  • For print( 0, func1() ), the multi-valued return value 1 2 is processed as is.

This is also the case for things like List constructors.

// @lnsFront: ok
fn func1(): int,int {
   return 1, 2;
}
let list1 = [ func1() ]; // [1 2]
let list2 = [ func1(), 10 ];  // [ 1 10]
let list3 = [ 0, func1() ];  // [0 1 2]

Even if () is attached to a multivalued function call as shown below, only the beginning of the multivalued return value is processed.

print( (func1()) );

This is behavior according to the Lua specification rather than the LuneScript specification.