- PVSM.RU - https://www.pvsm.ru -

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

Lua [1] — это, пожалуй, мой любимый "маленький язык", с низкой когнитивной нагрузкой и простотой в изучении и использовании. Он встроен во многое ПО, такое как Redis [2], NGINX через OpenResty [3] и Wireshark [4] (прим. перевод.: и многие другие [5]). Он также используется в качестве скриптового языка в таких играх, как World of Warcraft [6] и Roblox через Luau [7] (прим. перевод.: и многих других [8]). Этот пост — краткое признание в любви языку с некоторыми примерами того, почему он мне так нравится.

Логотип языка программирования 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 [9], MoonScript [10] и Fennel [11].

В качестве краткого примера, вот тот же бинарный поиск написанный в 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) [12] или Go (c GopherLua) [13]. Однако, одной из самых популярных реализаций является скриптовый язык Luau для игры Roblox [14].

Возможно, одно из моих любимых применений 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([[
        <html>
            <body>
                <p>%s<br>&nbsp;&nbsp;--%s</p>
            </body>
        </html>
    ]], 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
    <html>
    	<body>
    		<p>Do not wait to strike until the iron is hot; but make it hot by striking.<br>&nbsp;&nbsp;--William B. Sprague</p>
    	</body>
    </html>

Зачем вообще так делать? Легко вообразить ситуацию, в которой нужна небольшая логика приложения поверх веб-сервера, но вы не хотите писать полноценной веб-приложение под эту задачу. Или же вы хотите расширить функционал существующих приложений, например, добавить небольшой запрос (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 (см. доки [15]) для перехвата информации о запросе, его изменения или добавления дополнительных возможностей аутентификации поверх ПО без встроенной системы аутентификации.

Сообщество

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

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

Прим. перевод.: В отличие от некоторых сообществ, в Lua нет ни мелких, ни больших скандалов или интриг (см. Rust 👀: ^1 [22], ^2 [23], ^3 [24])

Заключение

Есть ли какой-то вывод? Lua очень хорош, вы можете освоить его за выходные [25] и начать использовать его для написания слоёв авторизации в HAProxy, аддонов для World of Warcraft, игр в рамках Roblox, скриптов для вашего оконного менеджера [26], работы с сетями [27] или просто небольших библиотек, которые делают вас чуточку счастливее.

Прим. перевод.: и не забывайте про игровые движки - Love2D [28], Defold [29] и интеграция с Raylib [30].


От себя я бы рекомендовал посмотреть на Fennel [31], упомянутый выше в переводе. Это LISP, который транспилируется в Lua — вы получаете скорость, простоту и доступность Lua c гибкостью LISP-синтаксиса и макросов.

А для тех, кто только начинает свой путь в программировании, у меня есть промокод HABR23. Где применить не скажу, а то реклама :)

Кстати, я веду Телеграм-канал для заинтересованных в обучении и осмыслении всего в IT, присоединяйтесь, если ещё не! [32]

Автор: Novus Nota

Источник [33]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/razrabotka/385066

Ссылки в тексте:

[1] Lua: https://www.lua.org/about.html

[2] Redis: https://redis.io/docs/manual/programmability/eval-intro/

[3] NGINX через OpenResty: https://openresty.org/en/

[4] Wireshark: https://wiki.wireshark.org/Lua

[5] и многие другие: https://en.wikipedia.org/wiki/List_of_applications_using_Lua

[6] World of Warcraft: https://wow.gamepedia.com/Lua

[7] Roblox через Luau: https://luau-lang.org/

[8] и многих других: https://en.wikipedia.org/wiki/Category:Lua_(programming_language)-scripted_video_games

[9] Terra: http://terralang.org/

[10] MoonScript: https://moonscript.org/

[11] Fennel: https://fennel-lang.org/

[12] Javascript (с Fengari): https://fengari.io/

[13] Go (c GopherLua): https://github.com/yuin/gopher-lua

[14] Luau для игры Roblox: https://create.roblox.com/docs/scripting/luau

[15] доки: https://manpages.ubuntu.com/manpages/impish/man1/haproxy-lua.1.html

[16] LuaRocks: https://luarocks.org/

[17] быстрого парсинга и кодировки JSON: https://luarocks.org/modules/openresty/lua-cjson

[18] дополнений к стандартной библиотеке: https://luarocks.org/modules/tieske/penlight

[19] программируемых компьютеров в Minecraft: https://tweaked.cc/

[20] Лиф Коркоран (Leaf Corcoran): https://leafo.net/

[21] Lapis: https://leafo.net/lapis/

[22] ^1: https://thephd.dev/i-am-no-longer-speaking-at-rustconf-2023

[23] ^2: https://www.jntrnr.com/why-i-left-rust/

[24] ^3: https://gist.github.com/fasterthanlime/42da9378768aebef662dd26dddf04849

[25] освоить его за выходные: https://tylerneylon.com/a/learn-lua/

[26] скриптов для вашего оконного менеджера: https://awesomewm.org/

[27] работы с сетями: https://nmap.org/

[28] Love2D: https://love2d.org/

[29] Defold: https://defold.com/

[30] интеграция с Raylib: https://github.com/tsnake41/raylib-lua

[31] Fennel: https://fennel-lang.org

[32] присоединяйтесь, если ещё не!: https://t.me/novusnota

[33] Источник: https://habr.com/ru/articles/738414/?utm_source=habrahabr&utm_medium=rss&utm_campaign=738414