Немного о лунном.

Posted on June 18, 2014 with tags: lua.

Лунный примечателен тем что это очень простой язык. Несмотря на такие языковые возможности как анонимные функции и замыкания это очень традиционный процедурный язык. Языковые конструкции в нём означают примерно то чего от них и ждёшь. Его простота столь велика что определённым образом провоцирует беспечность программиста. В следствие этой беспечности столкновения со специфичными для языка вещами обычно бывают особенно болезненны и полны детской обиды. Здесь перечислено несколько элементарных моментов, столкновение с которыми у программиста не на лунном может вызвать недоумение и предварительное ознакомление с которыми должно упростить вхождение в язык тем кто так и не прочёл документацию1:

  1. Массивы индексируются начиная с единицы2.

    v = { 0, 1, 2, 3 }
    
    for i = 0, #v do
        print( v[i] )
    end
    
    -- nil
    -- 0
    -- 1
    -- 2
    -- 3
  2. Все переменные глобальные, если не указано что они локальные3.

    x = 1
    y = 13
    
    z = function()
        x = 0
        local y = 0
    end
    
    print( x, y )
    z()
    print( x, y )
    
    -- 1    13
    -- 0    13
  3. Захват переменных в область видимости (в том числе и в замыкания) осуществляется по ссылке или по имени4. Таким образом можно невозбранно модифицировать переменные из замыкания в которое они захвачены. Но при передаче значения в функцию в качестве аргумента создаётся новая локальная ссылка и изменения переменной во внешней области видимости происходить не будет5.

    x = 1
    y = 13
    
    z = function( v )
        v = 0
        y = 0
    end
    
    print( x, y )
    z( x )
    print( x, y )
    
    -- 1    13
    -- 1    0
  4. Если инициализировать локальную переменную анонимной функцией, то она не будет доступна внутри области видимости анонимной функции. Таким образом мы вполне можем вызывать такую функцию рекурсивно6.

    f = function( x )
        f( x )
    end
    
    -- Всё в порядке. Путь к переполнению стека открыт.
    
    local f1 = function( x )
        f1( x )
    end
    
    -- Всё плохо.
    
    local f2
    f2 = function( x )
        f2( x )
    end
    
    -- А вот так, как не странно, всё хорошо.
  5. В lua свои собственные особенные регулярные выражения7.

    • Эскейпинг магических символов осуществляется при помощи ‘%’.
    • Символы пунктуации нуждаются в эскейпинге.
    • ‘-’ это такой ленивый ‘*’.
    • Ну и расширений по мелочи.
  6. Знак неравенства. Поскольку все современные промышленные языки программирования так или иначе произошли от C (Java и C#, например), у программиста нет сомнений относительно того как должен выглядеть оператор неравенства — !=. А между тем из числа арифметических операторов и условных операторов форма этого оператора пожалуй наиболее непостоянна: /= в Haskell, <> в Pascal и PHP. лунном неравенство это неожиданно ~=8.

    print( 1 != 2 )
    
    -- Ошибка компиляции
    
    print( 1 ~= 2 )
    
    -- А вот так просто true
  7. Форматированный вывод чисел. Поскольку в лунном языке нет целых чисел и вместо них используют числа с плавающей запятой двойной точности, то переполнение величины в 251 приводит к потере точности целой части. Однако при преобразование числа в строку, переход от целочисленной к экспоненциальной записи происходит раньше этого предела. Этот переход ошибочно воспринимается некоторыми как потеря точности в целой части, хотя это не так и чинится настройками форматирования.

    print( 2 ^ 51 )
    
    -- 2.2517998136852e+15
    
    print( string.format( "%.0f", 2 ^ 51 ) )
    
    -- 225179981368528
  8. Nil не может быть последним элементом массива. Если последним элементом массива оказывается nil, массив укорачивается на этот элемент.

    list = { 1, 2, 3 }
    
    list[2] = nil
    
    print( table.unpack( list ) )
    
    -- 1    nil    3
    
    list[3] = nil
    
    print( table.unpack( list ) )
    
    -- 1
    
    print( #{ nil, nil, nil } )
    
    -- 0

Также если вы кондовый сишник, вам безусловно очень понравятся begin и end, но это уже совсем другая история.

P.S: Запись основана на реальных событиях. Каждым из пунктов прострелил себе колено я или кто-то из моих знакомых.

P.P.S: Продолжение следует.


  1. Важно понимать, что никакой тайны из этих вещей не делается. Наоборот, все эти вещи описаны в официальной документации к языку. Она у него кстати довольно короткая. Просто все её читают в лучшем случае по диагонали.

  2. Читать здесь где-то начиная с «We use the term sequence…».

  3. Читать здесь начиная с «…Any variable name…».

  4. Все глобальные переменные связываются по имени. Если точнее то все глобальные переменные просто элементы хэштаблицы _ENV и связывание с ними это поиск значения в этой таблице по имени. Факт этот настолько поразил меня, что не упомянуть о нём совсем я не мог, хотя он и выходит за рамки обсуждения.

  5. Читать здесь начиная с «…function’s formal parameter…».

  6. Читать здесь начиная с «…The statement local…».

  7. Читать здесь всё.

  8. Читать здесь начиная с «…The operator ~=…».