ShiVa Lua unlocked Pt.5: variadic and anonymous functions, currying, pseudo classes – ShiVa Engine

ShiVa Lua unlocked Pt.5: variadic and anonymous functions, currying, pseudo classes

Welcome back to another installment of the Lua Unlocked series. In this post, we are going to dive a bit deeper into tables and functions, and see what we can build when we combine the two in a certain way.

But first, the usual disclaimer for all of the tutorials in this series:

All methods described here make use of Lua 5.0.x language features, which will not translate into C++. If you decide to use any of them, you will not be able to make use of ShiVa's C++ translator and waive potential performance benefits.

Furthermore, you will greatly confuse the ShiVa syntax highlighter and "compiler" (syntax checker) in ShiVa 1.9.2. Any undocumented code will not be colored in properly, and your log will be full of warnings and errors which you must consciously ignore. This can make debugging "regular" code more complicated.

Variadic Functions

In mathematics and in computer programming, a variadic function accepts a variable number of arguments. This can be useful for a number of cases where you do not know the number of arguments passed to a function. A classic example would be text concatenation.

Variadic functions take three dots "..." as arguments, which translate into an arg table, which must be unpacked:

  1. function lua2.variableParams ( ... )
  2. --------------------------------------------------------------------------------
  3.  
  4. for i, v in pairs(arg) do
  5. log.message ( v )
  6. end
  7.  
  8. --------------------------------------------------------------------------------
  9. end

What do you think the output of this call will be?

  1. this.variableParams ( "p1", "p2", "p3" )

Contrary to your intuition, 4 results will be logged: p1, p2, p3, 3, which is a list of all the arguments as well as an addition int for the number of arguments. To prevent the last number from being processed, you need to include the following check:

  1. for i, v in pairs(arg) do
  2. if next(arg,i) ~= nil then
  3. log.message ( v )
  4. end
  5. end

You can also combine the three dots with named parameters, such as:

  1. function lua2.variableParamsPartly ( vVal1, vVal2, ... )
  2. --------------------------------------------------------------------------------
  3.  
  4. log.message ( "partly defined 1: ", vVal1 )
  5. log.message ( "partly defined 2: ", vVal2 )
  6.  
  7. for i, v in pairs(arg) do
  8. if next(arg,i) ~= nil then
  9. log.message ( "partly loop: ", v )
  10. end
  11. end
  12.  
  13. --------------------------------------------------------------------------------
  14. end

A call would look like this:

  1. this.variableParamsPartly ( "v1", "v2", "v3", "v4" )

Without the next(arg,i) check, the last log would be the number of unnamed arguments - so in the call above, that number would be 2, even though we passed 4 total arguments to the function.

Anonymous functions

An anonymous function (often called lambda expression) is a function definition that is not bound to an identifier. If the function is only used once, or a limited number of times, an anonymous function may be syntactically lighter than using a named function. Anonymous functions are ubiquitous in functional programming languages.

Technically, you were using anonymous functions the entire time you have been working with ShiVa: Because in Lua, all functions are technically anonymous. A named function in Lua is simply a variable holding a reference to a function object. These two statements express the same thing:

  1. local result = function timesTwo(x)
  2. return 2*x
  3. end
  4.  
  5. local timesTwo = function(x) return 2*x end

Since functions are just variables, you can easily put them inside other existing functions:

  1. function lua2.onInit ( )
  2. --------------------------------------------------------------------------------
  3.  
  4. -- do something here
  5.  
  6. _return = function () do_something_else end
  7. return _return
  8.  
  9. --------------------------------------------------------------------------------
  10. end

A tree of single argument functions within a single argument function has a special name and allows for some interesting syntax: Welcome to currying!

Currying

Currying is the technique of translating the evaluation of a function with multiple arguments into evaluating a sequence of functions, each with a single argument. The name is a reference to logician Haskell Curry, who also lends his name to the Haskell functional programming language, where this pattern of programming is much more common.

Take for example the sum of two numbers. A classic function would take both numbers and return the result:

  1. function lua2.normalSum ( number, anothernumber )
  2. --------------------------------------------------------------------------------
  3.  
  4. return number + anothernumber
  5.  
  6. --------------------------------------------------------------------------------
  7. end

The curry version would only take a single argument at a time and evaluate the second argument in its inner anonymous function:

  1. function lua2.currySum ( number )
  2. --------------------------------------------------------------------------------
  3.  
  4. return function(anothernumber)
  5. return number + anothernumber
  6. end
  7.  
  8. --------------------------------------------------------------------------------
  9. end

The call signature for both functions is quite different:

  1. this.normalSum (5, 2)
  2. this.currySum (5) (2)

You can "curryfy" existing functions quite easily, using a generic translation function, like this one for any function with 2 arguments:

  1. function lua2.curry ( f )
  2. --------------------------------------------------------------------------------
  3.  
  4. return function (x)
  5. return function (y)
  6. return f(x,y)
  7. end
  8. end
  9.  
  10. --------------------------------------------------------------------------------
  11. end

Here is an example of using the curry version of the power function in the ShiVa API:

  1. -- curry
  2. local power = this.curry ( math.pow )
  3. log.message ( power (3) (4) )

Combining lambdas and curry, you could write a specialized and reusable power2-function in 3 lines:

  1. local power = this.curry ( math.pow )
  2. local _pow2 = function(x) return power(x)(2) end
  3. log.message ( _pow2(8) )

However, the real power of this approach comes in the form of partial application.

Partial Application

As you might have suspected, power (3) (4) are actually two function executions, one for the outer function (3), and one for the inner function (4). Since you execute both parts separately, this opens the doors to some really interesting design patterns.

Consider for example a counter function that ticks up by 1 every time it is called. In ShiVa, you would either have to create a local variable at the top of your script which you have to track, or an AI member variable which clutters up your AIModel. With Partial Application, you can write a function like this:

  1. function lua2.localCounter ( )
  2. --------------------------------------------------------------------------------
  3.  
  4. local i = 0
  5.  
  6. -- inner lambda
  7. return function ()
  8. i = i + 1
  9. return i
  10. end
  11.  
  12. --------------------------------------------------------------------------------
  13. end

The variable i is initialized in the outer function, while the counter logic is inside the inner function. If you call the function through Partial Application, i will count up and will not be reset:

  1. local lCounter = this.localCounter ( )
  2. log.message ( lCounter() ) -- i=1
  3. log.message ( lCounter() ) -- i=2
  4. log.message ( lCounter() ) -- i=3

We are effectively executing this.localCounter ( ) ( ) by storing this.localCounter ( ) into local lCounter and then executing the inner function lCounter(). Since the outer function is cached in a local variable, it will not reset when we call the inner function.

Lambdas in Tables

Since our last Lua Table tutorial, you should be familiar with table constructions like this:

  1. local Table = {Apple = "Macintosh", Int_thing = 55, Letters = {a = "a1", b = "b2"}}
  2. log.message ( Table.Apple ) -- "Macintosh"
  3. log.message ( Table.Letters.a ) -- "a1"

Tables can also store references to function objects:

  1. local toolset = { ab = math.abs, si = math.sin }
  2. log.message ( toolset.ab(-35) ) -- 35
  3. log.message ( toolset.si(90) ) -- 1

To make these tables reusable, you can store them inside AI member functions:

  1. function lua2.toolset ( )
  2. --------------------------------------------------------------------------------
  3.  
  4. return {
  5. ab = math.abs,
  6. si = math.sin
  7. }
  8.  
  9. --------------------------------------------------------------------------------
  10. end

Then call them like a standard ShiVa function:

  1. local tools = this.toolset ( )
  2. log.message ( tools.ab(-35) )
  3. log.message ( tools.si(90) )

Pseudoclasses

Putting everything we have learned so far together - lambdas, partial application, function objects in tables - we can build something quite interesting: a pseudoclass. There are several approaches to bringing class-like objects to Lua, for instance through metatables, however I found the following method the most convenient for ShiVa.

Let's start by extending the Partial Application sample with variables, API functions and new self-defined lambda functions, and putting it all inside a table:

  1. function lua2.pseudoClassAI ( )
  2. --------------------------------------------------------------------------------
  3.  
  4. return {
  5. -- variables
  6. _mStr = "teststring",
  7. _mInt = 55,
  8.  
  9. -- API functions
  10. _fAbs = math.abs,
  11. _fSin = math.sin,
  12.  
  13. -- self defined functions
  14. _fSquare = function ( nNum )
  15. return math.pow ( nNum, 2 )
  16. end,
  17.  
  18. _fCube = function ( nNum )
  19. return math.pow ( nNum, 3 )
  20. end
  21. }
  22.  
  23. --------------------------------------------------------------------------------
  24. end

You can instantiate and use the class like this:

  1. local PseudoClass = this.pseudoClassAI ( )
  2.  
  3. log.message ( PseudoClass._mStr );
  4. log.message ( PseudoClass._fAbs(-12) );
  5. log.message ( PseudoClass._fSin(270) );
  6. log.message ( PseudoClass._fSquare(15) );
  7. log.message ( PseudoClass._fCube(32) );

You can instantiate the pseudoclass from the same member function as often as you like. The member variables are not shared:

  1. local PseudoClass2 = this.pseudoClassAI ( )
  2. PseudoClass2._mStr = "derp"
  3. log.message ( PseudoClass._mStr ) -- "teststring"
  4. log.message ( PseudoClass2._mStr ) -- "derp"

Unfortunately, these pseudoclasses would be destroyed when you reach the end of the parent function / frame. To prevent this prom happening, you have to add the class to the global _G table:

  1. rawset(_G, "pseudoG1", this.pseudoClassAI ( ))
  2. rawset(_G, "pseudoG2", this.pseudoClassAI ( ))
  3.  
  4. pseudoG1._mStr = "first class"
  5. pseudoG2._mStr = "second class"
  6.  
  7. log.message ( pseudoG1._mStr ) -- "first class"
  8. log.message ( pseudoG2._mStr ) -- "second class"

Now all scripts in your application have access to the pseudoclass you defined.