mr_aleph:
Я понял что голое Lua стало меня в некоторой степени раздражать ... Я, как человек, которого голое Lua раздражало с первого дня не мог не ответить :)
Луа идеальный язык-компаньон для С. Он немерянно крут в этом качестве - он добавляет в С все, чего у него нет (для написания компиляторов особенно ^_^): первоклассные функции с ТСО, быстрые hash-tables с ключом любого типа, инкрементальный GC (т.е. memory-safety), корутины, symbols-atoms, кое-какие regexp'ы и настоящий быстрый PEG-parser.
Но с "lua как функциональный язык" или с "lua как VM" не все так хорошо. Меня лично больше всего раздражает:
- Отсутствие быстрого иммутабельного tuple-дататипа - крайне желательно, унифицированного с аргументами функции.
Первым следствием этой проблемы является невозможность реализации эффективных иммутабельных структур данных. Нельзя иметь банальный cons-лист, который был бы быстрым и эффективным по памяти: создайте в луа миллион таблиц, потом в нормальной реализации Scheme - миллион cons-ов и почувствуте разницу.
Для лечения я долго заигрывал c идеей иметь pair как 2-upvalue у некой функции - это слегка экономит память, но чтобы не таскать с собой библиотеку (Hyperlua теперь не использует ни грамма постороннего рантайма для своей работы) плюнул и выстрадал:
['x','y','z'] ---> {'x',{'y',{'z',"#null"}}
Вообще языков, где аргументы функций не tuple, я вообще не понимаю - это однозначный дефект. При этом он есть даже в Scala (и называется Одерски одной из ошибок проектирования).
В lua есть некая полупервоклассная сущность - stack, которая типа вместо tuple, и в результате мы имеем ужас local a,b,c = 1,2, который непонятно зачем нужен, отсутвие сколько-нибудь определенной арности у функции и.т.д
- Вторая серьезная проблема языка: отсутствие прозрачной персистентности (oсобенно стека у корутин). Есть велосипед Pluto - но это должно быть в языке, проверено на прочность тыщщами програмистов. Erlang и даже Stackless Python выглядят тут намного лучше.
Остальное в целом лечится (дальше идет попытка по пунктам прокомментировать пожелания
mr_aleph):
mr_aleph: хочется нормального способа итерации и table comprehensions, в какой-нибудь форме:
Мне тоже хотелось, но честно говоря не настолько чтобы превозмогать синтаксическую боль. Однозначно нужно писать библиотеку с map foldl/r zip etc, но не очевидно, что нужно писать flatMap, значит это не монада, значит можно обойтись простыми функциями :).
local function make_label (ix)
local t = {}
for i = 1, #ix do
local str = instr_to_short_string(ix[i])
if str then table.insert(t, str) end
end
return table.concat(t, '\\n')
end
Ваш пример это более-менее map, при наличии сахара можно переписать a-la F#:
local lable = ix
|> imap, fn x => instr_to_short_string(ix[i]) or false end
|> ifilter, fn x => x end
|> table.concat,'\\n'
... результатом будет:
local lable =
table.concat(ifilter(imap(ix,
function(x)
return instr_to_short_string(ix[i]) or false
end),
function(x) return x end),
'\\n');
(... и наверное имеет смысл написать mapNotNil).
mr_aleph: ADT, pattern matching
О, это да! Я пару лет нырял в эти глубины: PM для языка с динамической типизацией. Последние нововведения: nullable-типы, or/and/isa-типы и структурная типизация userdatы. Пока я сломался на возвратах функции, я просто не представляю как задавать контракты для функции, которая в каждой клаузе может вернуть все что угодно. Я посмотрел соответствующие работы по Erlang и пока пришел к выводу что проще новый язык написать.
local function is_jump (i)
return i.i ~= nil and (i.i.t == 'j' or i.i.t == 'j?')
end
Ваш пример перепишется (хотя на таком примере много не сэкономишь) так:
local fun is_jump
| {i={t=('j'|'j?')}} -> true
| _ -> false
end
... результат:
local function is_jump(i)
if type(i)=='table' and #i==0 and i.i~=nil and
type(i.i)=='table' and #i.i==0 and i.i.t~=nil
then
return (i.i.t=='j' or i.i.t=='j?')
else return false
end
end
mr_aleph: Более удобный способ итерации по таблицам, с одновременной модификацией других таблиц. Сюда же отнести функциональные методы выражения итерации (map, foldl) --- они нужны в стандартной библиотеки и нужен короткий способ записи лямбд.
Такой библиотеки от авторов языка никогда не будет, это противоречит философии языка, но ее просто написать. Короткий способ записи лямбд есть даже в виде сишного патча.
Я для лямбд в разное время порождал разные варианты, к сожалению самые короткие, типа \x=>x*x не годятся (их приходится все время писать в скобках, поскольку не всегда понятно где заканчивается выражение и - еще хуже - это приводит к 'тонким' ошибкам). Сильно короче, чем fn x => x * x end к сожалению не выходит.
mr_aleph: Более удобную систему модульности и более удобный способ связать "тип" и функции манипулирующие им.
Это больное место,
Норман Рэмси про это на Lua Workshop говорил.
Мне хотелось бы без танцев с бубном добавлять в язык первоклассные типы, но lua не
Cola и никогда ей не станет.
Тип и функции довольно неплохо связываются через метатаблицы, по-быстрому можно подхачить ad-hoc операторами a-la Haskell (тут я пошел дальше металуа и разрешил определять операторы примерно как Scala/Ocaml - но это не самая нужная вещь на свете).
Еще у меня возникла проблема с определением того, что можно вызывать у userdata - в результате я добавил в pattern-matching indexable type: {|...|}:
local fun Drag
| button@{| onDrag=handler:'fun' |}, x,y -> handler(button,x,y)
| _,_,_ -> --NOOP
end
... превращается в
local function Drag(button, x, y)
if (type(button)=='table' or (getmetatable(button) and
getmetatable(button).__index)) and button.onDrag~=nil and
type(button.onDrag)=='function'
then
return button.onDrag(button, x, y)
else
end
end
mr_aleph: Некоторые встроенные "типы", например, "множества".
По моему это чисто библиотечная проблема, в PIL написано даже как операторы для множеств переопределить.
Подытоживая, провозившись со всем этим некоторое время, могу сказать что:
- pattern-matching правда очень удобен
- comprehensions это роскошь; iota, map, zip - другое дело. Для майнстрим языка правда, возможно, находится где-то в районе Scala/Linq/F#.
- было бы неплохо если появились бы стандартные FP-like библиотеки для array, list, set etc, но все попытки имеют тенденцию быть монструозными и все равно нестандартными. Например, какая сигнатура должна быть у imap: (a->b),{a} -> {b} или {a},(a->b) -> {b} или (a1...an)1*...*(a1...an) * ((a1*...*am)->b) -> (b1*...*bn)? К счастью завернуть все это для внутреннего пользования достаточно просто.