Problem
Existing basic types such as number and string require monkeypatching to add member functions. Tables can have any function as a member that can be called with the dot operator(.) or the colon operator(:). The dot operator provides access to all objects that are stored in the table, functions or attributes. The colon operator allows to call a function that is part of a table, and pass the table itself as the first parameter.
The solutions for basic types and tables have some downsides. The dot operator requires the table itself as a parameter, the colon operator still requires the function as a value of the table itself, monkeypatching is powerful but can have global consequences[1][2] that are hard to manage.
In all other cases you have to use a function as a free function with the value as a parameter. While it is not a big deal for single function calls, the chaining of functions will always lead to a reverse call order:
round( cosh( math.exp( 1 ) ), 7 )
This is inconvenient to read and the only thing that is required in this case is to pass the result of the function as the first parameter to the following function.
Solution
A new arrow operator(->) would allow to write the functions in the same reading order as they are called:
math.exp( 1 )->cosh()->round( 7 )
The "->" operator passes the value/result on the left side as the first parameter of the function on the right side. This behaviour would be quite similar to the existing colon operator for tables, without the requirement that the function is part of a table.
If a function returns two or more values, the "->" operator just passes to the first value to the following function. The additional "=>" operator would allow to pass two values to the following function.
local function twovalues() return "a", "b" end twovalues->print() -- prints: "a" twovalues=>print() -- prints: "a\tb"
If a function returns just one value, the "=>" operator should pass a nil as a second value. Lua allows more than two return values, but I have difficulty to imagine many cases with more than two values where someone would want to write code in that way.
It may look like that tables do not benefit much from the new arrow operators, but the ability to use non-member functions like member functions with "->" can potentially reduce the temptation to add functions as member, and allows to call all functions like member functions in C++ - improving the code in general as a result.[3].
Just to be clear this proposal has nothing to do with the arrow functions you maybe know from JavaScript[4]. The idea is based on the hidden parameter behaviour that the ":" operator uses.
Safe Navigation
The simple injection of function calls allows us also to create functions that are used like a language extension. Lets take the safe navigation operator example from chapter 5.5 in "Programming in Lua Fourth edition"[5]. The recommended replacement for the "?." operator in C# looks like this:
E = {} zip = (((company or E).director or E).address or E).zipcode
With the arrow operator it is possible to imagine the following solution:
function sv( v ) return v or {} end zip = company->sv().director->sv().address->sv().zipcode
This approach adds three function calls, but I find this code easier to read and write.
Example
The example below should demonstrate how the use of the arrow operators is imagined:
-------------------------------------------------------------------------------- -- is module -------------------------------------------------------------------------------- local counter = 0 local function printok( b ) counter = counter+1 if b then print( "ok "..counter ) else print( "not ok "..counter ) end end local function ok( v ) printok( v ) end local function nok( v ) printok( not v ) end local function eq( a, b ) printok( a == b ) end local is = { ok = ok, nok = nok, eq = eq } -------------------------------------------------------------------------------- --LuaZDF-begin --with cosh inside round startswith trim -------------------------------------------------------------------------------- --ZFUNC-cosh-v1 local function cosh( x ) return ( math.exp( x ) + math.exp( -x ) ) / 2 end --ZFUNC-inside-v1 local function inside( v, tab ) for _, val in ipairs( tab ) do if v == val then return true end return false end --ZFUNC-round-v1 local function round( x, n ) if x > 0 then return math.floor( x * math.pow( 10, n ) + 0.5 ) / math.pow( 10, n ) else return math.ceil( x * math.pow( 10, n ) - 0.5 ) / math.pow( 10, n ) end end --ZFUNC-startswith-v1 local function startswith( str, prefix ) local sub = string.sub( str, 1, string.len( prefix ) ) if sub == prefix then return true end return false end --ZFUNC-trim-v1 local function trim( str ) local n = str:find( "%S" ) return n and str:match( ".*%S", n ) or "" end -------------------------------------------------------------------------------- --ZFUNC-end -------------------------------------------------------------------------------- is.eq( 27.308233, round( cosh( 4 ), 6 ) ) cosh( 4 )->round( 6 )->is.eq( 27.308233 ) is.eq( 7.6101251, round( cosh( math.exp( 1 ) ), 7 ) ) math.exp( 1 )->cosh()->round( 7 )->is.eq( 7.6101251 ) is.ok( startswith( trim( " zztop " ), "zz" ) ) " zztop "->trim()->startswith( "zz" )->is.ok() local i = 4 is.nok( inside( i, { 1, 2, 3, 5 } ) ) i->inside{ 1, 2, 3, 5 }->is.nok()