Как стать автором
Обновить

Комментарии 27

Начало воодушевило, думал весь текст так легко написан, но в итоге кажется, что чтобы яснее раскрыть данную тему потребуется не одна статья такого объема.
Да, возможно. Могу лишь предложить поиграться в браузере, посмотреть примитивный kitten, реализованный на haskell, посмотреть Getting Started и примеры на сайтах самих языков, упомянутых в статье. Ну и я всё-таки рекомендую почитать Why Concatenative Programming Matters?[5], которую я даже перевел, надо лишь подготовить форматирование для хабрахабра — в этой статье всё очень подробно.
(define question
(display «Yahoo store has been written on common lisp»))
Спасибо тегу за отличную подсветку кода (:
Юзайте тег source
(define question
(display «Yahoo store has been written on common lisp»))
Пардон, ответ на комментарий выше.
спасибо, заменил в тексте все pre на source
НЛО прилетело и опубликовало эту надпись здесь
Да вы же забыли самый главный стековый язык! Если регистры добавить к стеку, то в асме только и происходит, что модификация этого стека конкатенацией инструкций!
Слова-то одинаковые, но стеки разные. :) Тот же 386 — регистровая машина.
В каком-то смысле да, если вы имеете ввиду работу с функциями
        push   offset msg
        call   printf
        push   0
        call   exit

но я побоялся ошибиться в этом вопросе, всё-таки виртуальная машина того же Python это очевидный байт-код, который вообще не оперирует регистрами.
>>> import dis
>>> def f(x):
...     return x + 1
>>> dis.dis(f)
2         0 LOAD_FAST                0 (x)
          3 LOAD_CONST               1 (1)
          6 BINARY_ADD
          7 RETURN_VALUE
Да, я вроде как по определению приписал регистры к стеку. В сущности они не особенно отличны от ячеек стека, я имею ввиду логически, а не конструктивно.
Есть такая вещь, как MUCK/MUD — многопользовательские текстовые ролевые игры, предки всех этих ваших WoW’ов. Есть для них популярный движок TinyMUCK. И есть в нём язык MUF — Multi-User Forth. Кто и зачем выбрал стековый язык, остаётся для меня загадкой (икал он, думаю, постоянно), потому что мне, как простому смертному объектно-процедурному программисту, совершенно непонятно, как можно на этом писать код. Шут с ней с польской нотацией, но бесконечное «а стек внезапно кончился»…

Хорошо ещё, что MUF нужен был только для сложных вещей, а для простых хватает MPI — write-only лиспо-подобного языка.

Собственно, возникает вопрос: а в каких случаях, собственно, оправдано написание кода на стековых языках? Когда это нужно? Когда это лучше и, если такое может быть, удобнее?
1. достаточно компактный код,
2. и достаточно быстрый,
3. это целая методология программирования — снизу-вверх интерактивно,
4. что позволяет легко реализовать и отладить DSL,
5. исполнители легко реализуемы (фактически — одна процедура с большим case и простыми командами)
6. и поэтому легко переносимы (по сравнению например с VM на регистровом пуле).
7. ну и, когда привыкаешь — затягивает.
Никто не спорит, что на стековом языке легко написать нечитаемые программы, непривычный код, тем более на таком относительно низкоуровневом и устаревшем языке с простым IDE, без системы типов и кучи проверок на этапе компиляции. Автор старается обьяснить, что нужно правильно организовывать свои программы.
Конкатенативные языки неплохо подходят для такого dataflow-программирования и с согласен с shambho, это быстрый и компактный язык, код на котором легко рефакторится и оптимизируется. А современные ещё и очень интересны в функциональном плане, о чём я, по-видимому плохо, пытался рассказать. Неважно, что это стековый язык, можно думать в функциональном стиле — писать чистые функции, тестировать их (в том же Factor есть юнит-тесты), создавать модули и библиотеки. Но при этом нет бесконечных скобочных аппликативных вызовов (a(b(c,d,e(f(g(g))))), а есть порядок функцией в соответствии с потоком данных g dup f e d c b a. Если каждая функция описана, ясна и протестирована, а компилятор следит корректностью, то тогда всё нормально.
Мне кажется, что невозможность понять арность функций в выражении «g dup f e d c b a» без просмотра сигнатур оных — уже солидный изъян. Читабельность страдает. Кстати, это выражение должно быть записано как a(b(c(d(e(f(g() g())))))) или, если вы имели ввиду Lisp, как (a (b (c (d (e (f (g) (g))))))). dup ведь копирует вершину стека, так ведь?
Да, действительно, большое спасибо за поправку g() g(), именно пример функции с двумя аргументами. А если разбавить всё это управляющими конструкциями, паттерн маттчингом на несколько строк, то определение унарности функции всё равно требует мысленного синтаксического разбора, который, как мне думается, создает лишь иллюзию повышенной читаемости кода. Дело привычки и только потому, что мозг «кеширует» эти результаты разбора, в данном определенном контексте уже знает сигнатуры каждой функции и с кажущейся легкостью оперирует ими. Но тот же эффект происходит и при чтении больших программ с композицией функций — необязательно в конкатенативном языке, и в функциональных языках с бесточечной нотацией и возможностью каррирования.
Конечно, IDE очень сильно может способствовать чтению кода, как и тот факт, что правильно построенный модульный код с несвязанностью позволяет держать «в кэше» лишь небольшую часть функцию.
Однако, например для математических формул со множеством операций, такая форма записи может быть крайне неудобной по чисто историческим причинам. Мы привыкли к инфиксной форме, то есть нет смысла ломать себя в том, чему мы умеем с детства и умеем мнгновенно — читать такие выражения хочется в инфиксной форме и тут не обойтись использованием переменных. Но тот же Factor, написанный на самом языке использует переменные менее чем в 1% своего исходного кода.
Поддержу высказавшихся выше: dataflow очень хорошо реализуется на стековых языках, и вполне читаем даже незнакомыми с концепцией людьми.

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

Также добавлю про DSL: вообще, в стековых языках программа расширяет язык под задачу (т.к. встроенный синтаксис обычно минималистичен), в итоге текст программы может стать вполне литературным ) И при этом в процессе дополнения языка обычно растет уровень абстракции и на определенном уровне в стеке уже могут лежать http-запросы, выборки из БД, целые файловые системы. И этими сущностями манипулируют слова не менее высокоуровневые:
Примерно так может выглядеть сервер хранения статики:
взять_запрос запрос_картинки?
[dup вынуть_имя_файла достать_из_статики упаковать_картинку_в_ответ]
[drop "Отдаём только картинки" упаковать_строку_в_ответ] if
ответить
«Расширение языка» происходит и про объектно-ориентированном, и при процедурном программировании.

if (запрос_картинки(взять_запрос()))
    упаковать_картинку_в_ответ(достать_из_статики(вынуть_имя_файла(взять_запрос())))
else
    упаковать_строку_в_ответ("Отдаём только картинки")

if (запрос.взять().запрос_картинки)
    ответ.упаковать_картинку(статика.достать(запрос.взять().имя_файла))
else
    ответ.упаковать_строку("Отдаём только картинки")

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

У объектного и процедурного стиля вызов методов цепочкой выглядит не так опрятно, как в стековом для одноаргументных функций, но зато сразу, с высоты птичьего полёта (по началу строк), видна общая логика:

если (что-то там с запросом)
    упаковать картинку каким-то там способом
иначе
    упаковать строку каким-то там способом

Когда код нужно читать и анализировать, это очень сильно помогает.
Ось ООП/не-ООП и ось ФП/императив — практически ортогональны.
В стековом языке отлично реализуется ООП, если он нужен для задачи.

if перед ветками или после — это конкретная форма записи выражений: префиксная, или постфиксная. И она никак не связана с парадигмой ООП/не-ООП.
Вот в функциональных языках if/then имеет другой смысл — условная конструкция не осуществляет управление потоком исполнения, а является вычислимым выражением (и поэтому всегда имеет ветку else)

В стековом языке постфиксную запись ветвления можно превратить в префиксную, только это не нужно — язык то весь постфиксный!
А процедурное программирование — это просто программирование машины с Неймановской архитектурой. Т.е. если мы программируем последовательность действий, которые работают с памятью, наше программирование — процедурное.
Стек — память? -Да. Операции над стеком последовательны и меняют стек? -Да. Стековые языки — процедурные.
Существуют конкатенативные языки с прямой польской нотацией, например Om, по ссылке помимо устранения недостатка с затруднением чтения кода, который вы упомянули, ещё несколько важных преимуществ. А бывают и не только стековые. Посмотрел чуть ли не их все, о пока теоретический язык по пятой ссылке из топика самый привлекательный. С арностью можно таки совладать без IDEшных подстветок просто скобками, например сводя все к монадам:
[split ':'] '23:50:12'

Однако, пока не видел языка, синтаксис которого бы явно к такому склонял, скобками или переносом на новую строку.
Пока только начал читать про всё это, больше вопросов, чем ответов.
С каких это пор CFDG — конкатенативный язык? Он не отвечает определению из Википедии (ни русской, ни английсокой) и не похож на все эти Форт'ы.
Честно говоря, не помню, с чего я это взял. На форты он действительно не похож. Но у меня сложилось впечатление, что читал о нем ранее именно в этом ключе. Ну и язык не обязательно должен быть похож на FORTH и иметь обратную польскую запись, чтобы относиться к этой касте. Если ошибся, извините, что ввел вас в заблуждение.
Сначала это вообще был язык определения формальных грамматик. В новой версии в него добавили кучу фич, в т.ч. возможность определять функции и вводить переменные, и его уже можно назвать языком программирования. Но не конкатенативным.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории