Локальные функции

Локальные функции есть и в ActionScript и вполне удобны в некоторых случаях (я, например, реализовывал с их помощью паттерн Visitor для обхода и обработки рядов таблицы). Но использование локальных функций в haXe отличается строгой проверкой типов. Ниже материал из книги "Professional haXe and Neko".

Локальные функции безымянны. При создании локальной функции ссылка на нее присваивается переменной. С-программисты могу рассматривать это как некий аналог указателям на функции.

var functionVariable = function( param : ParamType [, ...] ) : ReturnType
{
  // код функции...
}

Когда локальная функция присваивается переменной, тип этой переменной определяется путем выявления типов, или же его можно указать явно. Типизация переменных, хранящих ссылки на функции, соответствует подходу, принятому в функциональных языках. Указав тип переменной, мы явно указываем, какие параметры должна принять функция и что она должна вернуть.

Переменная, хранящая ссылку на локальную функцию, описывается следующим образом:

var functionVariable : ParamOne [[-> ParamTwo] ...]-> ReturnValue;

Здесь указан тип каждого параметра функции. Параметры разделяются оператором ->. Выражение завершается типом возвращаемого значения, также отделенным оператором ->. Например:
var fun1 = function() : Void
{
  // код...
}
var fun2 = function( p1 : String ) : Void
{
  // код...
}
var fun3 = function( p1 : Int, p2 : Float, p3 : Int ) : String
{
  // код...
  return someString;
}

var fun1 : Void -> Void;
var fun2 : String -> Void;
var fun3 : Int -> Float -> Int -> String;

Главная фишка локальных функций это то, что они могут быть переданы как параметры другим функциям (методам класса или другим локальным функциям). При этом тип функции-аргумента должен быть указан в параметрах для принимающей функции.

var fun1 : String -> String;
fun1 = function( p1 : String ) : String
{
  // выполнить код...
  return someString;
}
var fun2 : String -> ( String -> String ) -> String;
fun2 = function( p1 : String, p2 : ( String -> String ) ) : String
{
  return p2( p1 );
}
var tmp = fun2( "someString", fun1 );

Таким способом можно реализовать причудливые рекурсии. Поскольку мы может определить тип переменной, не присваивая ей функцию, то можно создать две функции, которые будут принимать в параметрах друг друга и вызывать друг друга. Это удобно для обработки древовидных структур.

typedef RecursiveFunction = Int -> RecursiveFunction -> Int;

var add2 : Int -> RecursiveFunction -> Int;
var minus1 : Int -> RecursiveFunction -> Int;
add2 = function( p1 : Int, p2 : RecursiveFunction ) : Int
{
  p1 += 2;
  trace( p1 );
  if ( p1 < 5 ) p1 = p2( p1, add2 );
  return p1;
}
minus1 = function( p1 : Int, p2 : RecursiveFunction ) : Int
{
    p1--;
    trace( p1 );
    p1 = p2( p1, minus1 );
    return p1;
}
add2( 0, minus1 );
// Вывод: 2, 1, 3, 2, 4, 3, 5

Здесь одна функция прибавляет 2 к своему аргументу, а вторая вычитает 1. Они вызывают друг друга, пока не будет достигнут определенный лимит.

Каждая функция примимает вторым параметром функцию, принимающую вторым параметром функцию, принимающую вторым параметром функцию и т.д. То есть, само указание типа превращается в бесконечную рекурсию. Есть два способа определить такой тип:
1) использовать Dynamic (но при этом потерять все плюсы строгой типизации);
2) указать алиас для типа с помощью typedef.

В данном случае мы воспользовались вторым вариантом, описав тип RecursiveFunction. Как вы можете видеть, typedef позволяет описывать типы, ссылающиеся на самих себя.

Область видимости локальной функции включает:

  • статические параметры класса, в котором она определена;
  • локальные переменные, определенные до нее в том же блоке;
  • ее параметры;
  • и локальные переменные, опредленный внутри этой функции.

class LocalFunctionVars
{
  public static var myStaticVar1 : String;
  public var myVar1 : String;
  public static function main()
  {
    var myLocalVar1 : String;
    var myFunction = function()
    {
      var innerFunction : String;
      innerFunction = "haXe";
      myLocalVar1 = "is";
      myStaticVar1 = "really";
      // выбросит ошибку компилятора
      myVar1 = "amazing";
    }
  }
}

Здесь доступны большинство параметров класса, но не доступен не статический параметр myVar1. Доступ к таким параметрам обычно осуществляется через ссылку this, но this не доступен внутри локальной функции. Но это можно обойти с помощью небольшого хака. Благодаря тому, что локальной функции доступны локальные переменные, определенные до нее, ссылку this можно сохранить в такой переменной.
class LocalFunctionVars
{
  public static var myStaticVar1 : String;
  public var myVar1 : String;
  public static function main()
  {
    var l = new LocalFunctionVars();
  }
  public function new()
  {
    var myLocalVar1 : String;
    var me = this;
    var myFunction = function()
    {
      var innerFunction : String;
      innerFunction = "haXe";
      myLocalVar1 = "is";
      myStaticVar1 = "really";
      // теперь компилируется
      me.myVar1 = "amazing";
    }
  }
}