[Перевод] Lua: маленький язык, который смог

Lua — это, пожалуй, мой любимый «маленький язык», с низкой когнитивной нагрузкой и простотой в изучении и использовании. Он встроен во многое ПО, такое как Redis, NGINX через OpenResty и Wireshark (прим. перевод.: и многие другие). Он также используется в качестве скриптового языка в таких играх, как World of Warcraft и Roblox через Luau (прим. перевод.: и многих других). Этот пост — краткое признание в любви языку с некоторыми примерами того, почему он мне так нравится.

Логотип языка программирования Lua

Логотип языка программирования Lua

Простота

В Lua относительно немного фич и относительно мало синтаксиса. К примеру, в языке всего 8 типов:

  • nil

  • boolean

  • number

  • string

  • userdata (для представления С-шных структур данных или блоков памяти в куче)

  • function

  • thread (для корутин)

  • table (ассоциативный массив и, по совместительству, единственная встроенная структура данных)

Нет нужды беспокоиться о float, int, usize. Нет нужны беспокоиться о различиях в массивах, словарях и структурах. Даже классы тут это просто таблицы (table) с указанными мета-таблицами (metatables, прим. перев.: думайте о прототипах, как в JavaScript). Такая простота во всём делает Lua лёгким в освоении и использовании, обеспечивая при этом достаточную мощь для выполнения большинства необходимых задач.

Давайте реализуем простой бинарный поиск по массиву на Lua:

-- однострочные комментарии начинаются с двух тире

function binary_search(array, value)
    local low = 1
    local high = #array -- # это оператор взятия длины

    while low <= high do
        -- доступ к библиотечным функциям через глобальную таблицу
        local mid = math.floor((low + high) / 2)
        local mid_value = array[mid]

        if mid_value < value then
            low = mid + 1
        elseif mid_value > value then
            high = mid - 1
        else
            return mid
        end
    end

    return nil
end

res = binary_search({2, 4, 6, 8, 9}, 6)
print(res)

Всё это должно быть относительно знакомо, даже если вы никогда раньше не сталкивались с Lua. Единственное, что может показаться непривычным, это ключевое слово local, которое используется для объявления переменных (прим. перев.: и не только). По умолчанию все они глобальны, так что local используется для объявления локальной переменной относительно текущей области видимости.

Транспиляция

Lua является великолепной целью для транспиляции, благодаря его простоте и лёгкости взаимодействия с C. Поэтому, Lua — отличный выбор для предметно-ориентированных языков (DSL-ей), таких как Terra, MoonScript и Fennel.

В качестве краткого примера, вот тот же бинарный поиск написанный в MoonScript и Fennel соответственно:

Код

binary_search = (array, value) ->
    low = 1
    high = #array

    while low <= high
        mid = math.floor((low + high) / 2)
        mid_value = array[mid]

        if mid_value < value
            low = mid + 1
        else if mid_value > value
            high = mid - 1
        else
            return mid

    return nil

print binary_search {2, 4, 6, 8, 9}, 6
(fn binary-search [array value]
  (var low 1)
  (var high (length array))
  (var ret nil)
  (while (<= low high)
    (local mid (math.floor (/ (+ low high) 2)))
    (local mid-value (. array mid))
    (if (< mid-value value) (set low (+ mid 1))
        (> mid-value value) (set high (- mid 1))
        (do
          (set ret mid)
          (set low high)))) ; no early returns in Fennel
  ret)
(local res (binary-search [2 4 6 8 9] 6))
(print res)

Встраиваемость

Но истинная сила языка заключается в том, что вы можете внедрить его практически куда угодно — Lua реализован как библиотека для программы-хоста, типа Redis. Традиционно, это была программа на C, но теперь существуют многие реализации виртуальной машины Lua в разных языках, таких как Javascript (с Fengari) или Go (c GopherLua). Однако, одной из самых популярных реализаций является скриптовый язык Luau для игры Roblox.

Возможно, одно из моих любимых применений Lua — это HAProxy, возвращающий нас во времена Apache + mod_php скриптинга. Давайте настроим конфигурацию HAProxy, которая будет отвечать на запросы по определённому пути случайным предсказанием:

Код

local function fortune(applet)
    local responses = {
        {
            quote = "The only people who never fail are those who never try.",
            author = "Ilka Chase"
        },
        {
            quote = "The mind that is anxious about future events is miserable.",
            author = "Seneca"
        },
        {
            quote = "A leader is a dealer in hope.",
            author = "Napoleon Bonaparte"
        },
        {
            quote = "Do not wait to strike until the iron is hot; but make it hot by striking.",
            author = "William B. Sprague"
        },
        {
            quote = "You have power over your mind - not outside events. Realize this, and you will find strength.",
            author = "Marcus Aurelius"
        }
    }

    local response = responses[math.random(#responses)]
    local resp = string.format([[
        
            
                

%s
  --%s

]], response.quote, response.author) applet:set_status(200) applet:add_header("content-length", string.len(resp)) applet:add_header("content-type", "text/html") applet:start_response() applet:send(resp) end core.register_service("fortune", "http", fortune)
global
    lua-load fortune.lua

defaults
    retries                 3
    timeout http-request    10s
    timeout queue           1m
    timeout connect         10s
    timeout client          1m
    timeout server          1m
    timeout http-keep-alive 10s
    timeout check           10s

frontend fe_main
    bind :8080
    mode http
    http-request use-service lua.fortune if { path /fortune }

И затем запустим её:

$ haproxy -f haproxy.cfg
$ curl localhost:8080/fortune
    
    	
    		

Do not wait to strike until the iron is hot; but make it hot by striking.
  --William B. Sprague

Зачем вообще так делать? Легко вообразить ситуацию, в которой нужна небольшая логика приложения поверх веб-сервера, но вы не хотите писать полноценной веб-приложение под эту задачу. Или же вы хотите расширить функционал существующих приложений, например, добавить небольшой запрос (endpoint) для проверки статуса (healthcheck) Redis-сервера:

Код

-- это сторонний форк redis-lua с поддержкой TLS:
local redis = require("redis-tls")

local settings = {
	host = "127.0.0.1",
	port = 6379,
	database = 14,
	password = nil,
}

local utils = {
	create_client = function(params)
		local client = redis.connect(params.host, params.port, 1, false)
		if params.password then
			client:auth(params.password)
		end
		return client
	end,
}

local function redis_health(applet)
	-- pcall как try/catch, принимает функцию и аргументы,
	-- и возвращает true/false и результат выполнения функции
	local ok, client = pcall(utils.create_client, settings)
	if not ok then
		local string_resp = '{"ok":false}\n'
		applet:set_status(500)
		applet:add_header("content-length", string.len(string_resp))
		applet:add_header("content-type", "application/json")
		applet:start_response()
		applet:send(string_resp)
		return
	end

	local resp = client:ping()
	local string_resp = string.format('{"ok":%s}\n', resp)
	applet:set_status(200)
	applet:add_header("content-length", string.len(string_resp))
	applet:add_header("content-type", "application/json")
	applet:start_response()
	applet:send(string_resp)
end

core.register_service("redis_health", "http", redis_health)
global
    lua-load redis.lua

frontend fe_main
    bind :8080
    mode http
    http-request use-service lua.redis_health if { path /redis_health }

Redis-сервер выключен/лежит:

$ curl 127.0.0.1:8080/redis_health
  {"ok":false}

Redis-сервер работает и доступен:

$ curl 127.0.0.1:8080/redis_health
  {"ok":true}

Можно пойти дальше и использовать register_action и register_fetches (см. доки) для перехвата информации о запросе, его изменения или добавления дополнительных возможностей аутентификации поверх ПО без встроенной системы аутентификации.

Сообщество

Оно не особо велико, но в нём ведётся множество отличных разработок, а многие библиотеки доступны через простой пакетный менеджер LuaRocks. От библиотек для быстрого парсинга и кодировки JSON до дополнений к стандартной библиотеке (прим. перевод.: или даже добавления программируемых компьютеров в Minecraft) — для каждого найдётся что-то своё.

Отдельного упоминания заслуживает Лиф Коркоран (Leaf Corcoran), написавший Lapis — фантастический небольшой веб-фреймворк для дистрибутива OpenResty, на котором работает сайт LuaRocks.

Прим. перевод.: В отличие от некоторых сообществ, в Lua нет ни мелких, ни больших скандалов или интриг (см. Rust

© Habrahabr.ru