В начале 2000-х годов язык Рефал ещё как-то развивался, выходили новые версии компиляторов: РЕФАЛ-5, Рефал-6, была некоторая активность на сайте http://wiki.botik.ru/Refaldevel/, был написан компилятор SCP4. Я читал старый архив рассылки refal@botik.ru, в нём тоже были обсуждения Рефала и не только (например, сравнивали его с Си++). Потом, практически синхронно со смертью Турчина, активность спала практически до нуля (архива рассылки refal-devel в сети не нашёл, судить не могу).
В текущей рассылке (судя по новому архиву) в основном рекламируются конференции по метавычислениям, выкладываются сканы старых архивных материалов и Сергей Михайлович Абрамов поздравляет с Новым Годом.
Я правильно понимаю ситуацию, что Рефал появился не от хорошей жизни: Турчину нужен был инструмент (формальный язык) для выражения идей метавычислений, суперкомпиляции, существовавшие тогда Фортран и Алгол для этого не подходили, а о Лиспе в СССР никто тогда не знал? И теперь, когда появились новые, современные функциональные языки программирования, для реализации идей суперкомпиляции стали применять уже их? Те же Юрий Климов и Илья Ключников свои суперкомпиляторы писали отнюдь не на Рефале. А язык Рефал остался в прошлом.
И то, чем я занимаюсь (https://github.com/bmstu-iu9/simple-refal (смотреть ветку refal-5-lambda), https://github.com/Mazdaywik/mrefal) уже в проекте устарело на десятилетия? Не, своим хобби я заниматься продолжу, мне это интересно. Хоть и бесперспективно.
--
Коновалов Александр Владимирович, преподаватель
кафедра ИУ9 «Теоретическая информатика и компьютерные технологии»
МГТУ имени Н. Э. Баумана, Москва
Добрый день, Аркадий!
Большое спасибо за развёрнутый ответ. Сейчас я задам несколько развёрнутых вопросов. Заранее прощу прощения у подписчиков за многабукаф.
… а именно, с включением вложенных анонимных функций -- аналога лямбда-абстракции.…
Эта идея не моя, впервые она была предложена Сергеем Скоробогатовым в диалекте Refal-7 в 2006 году:
https://waybackmachine.org/web/20070813011422/http://iu9.bmstu.ru/science/refal.pdf
(даю ссылку на Wayback Machine, поскольку с сайта кафедры этот файл удалили). Вероятно, вы знакомы с ним (и со Скоробогатовым, и с диалектом), но ссылку я даю для всех читателей рассылки.
…(только поверхностно посмотрел доки)…
Основная дока (manul.pdf), к сожалению, (а) недописана, (б) частично неактуальна. Основной массив знаний (если углубляться) скрывается тут: https://github.com/bmstu-iu9/simple-refal/issues (включая закрытые задачи). Но на беглый просмотр там слишком много.
…было бы интересно и важно наработать множество примеров и техник с использованием этих новых средств, демонстрирующих их удобство и преимущество.
Пользуюсь вложенными функциями уже с 2009 года, мне нравится. Когда добавил их в язык, почувствовал прирост своей производительности, по сравнению с подмножеством Базисного Рефала. Пример их использования (несколько извращённый) вот:
https://github.com/bmstu-iu9/simple-refal/tree/master/doc/examples/inifile
Более нормальные примеры использования есть в исходных текстах самого компилятора.
Есть основания полагать, что Рефал создавался Турчиным именно как язык первого порядка, и в этом была интенция автора. … Рефал-5 -- это максимум того, что он был готов допустить.
Есть у меня идеи по статической типизации Рефала (но о них сейчас пока рано, через несколько месяцев будет в самый раз), и обдумывая их, я понял гений Турчина. РЕФАЛ-5 — это довольно компактное и выразительное подмножество, которое разрабатывалось не только и не столько для промышленного программирования (хотя писать большие программы на нём можно — SCP4), сколько для автоматического анализа. Поэтому и язык первого порядка, и нет неуспехов (но есть условия), и замкнутый набор встроенных функций. Мои идеи по выводу и проверке типов хорошо ложатся на РЕФАЛ-5, в то время как с функциями высшего порядка, неуспехами и сторонними функциями всё не так очевидно.
А в чём согласование с интенцией первого порядка таких особенностей, как выражения с двусторонним доступом, концепция поля зрения и динамическая типизация? Мне это немного не очевидно. И что подразумевается под явной активацией (мне не знаком этот термин)?
Вообще, я недавно начал смотреть лекции Абрамова по метавычислениям и задумался: а каким должен быть промышленный инструмент программиста для выполнения метавычислений? Подумалось, что это должен быть язык программирования и компилятор к нему, с хорошей поддержкой рефлексии (возможность как разобрать в рантайме AST любой функции, так и наоборот, её построить из AST и тут же вызвать), с возможностью интерпретации во время компиляции (как в C++14 constexpr), с возможностью сдампить набор функций в готовый для выполнения модуль. И если в такой среде реализовать специализатор как обычную библиотечную функцию, то вполне реализуются все три проекции Футамуры-Турчина. А потом вспомнил «пятикнижие Турчина» — описанный там язык и описанная там среда вполне подходят под это описание, разве что constexpr там нету.
Функциональные объекты этим условиям не удовлетворяют, они эктенсиональны: функцию можно вызвать, но нельзя препарировать, рассмотреть ее устройство.
Да, кстати. У меня (и у Скоробогатова в Refal’е-7) функциональные объекты сопоставляются с s-переменными.
Но конечно, это не значит, что последователи не вправе делать по-своему, с опорой на новые знания и опыт. И может быть, особенности Рефала дадут возможность по новому увидеть и понять функциональные объекты.
Я разрабатывал (и разрабатываю) диалект Рефал-5λ (который сначала назывался Простой Рефал), ориентированный на промышленное программирование. Вложенные функции повышают продуктивность программиста — они есть. Необходимо расширение возможностей языка — библиотека функций не закрыта, можно написать свою функцию на Си++. Даже больше. Есть удобный синтаксис для написания таких функций — вставки кода на Си++ (https://github.com/bmstu-iu9/simple-refal/issues/11, https://github.com/bmstu-iu9/simple-refal/blob/master/src/srlib/Library.sref). Т.е. не надо править и менять интерпретатор для написания ещё одной примитивной функции. Сейчас я работаю над унификацией с РЕФАЛом-5 (планирую сделать точное надмножество).
Что касается меня, как приложившего руку к созданию Рефала-6, могу только сказать, что в последние годы, уже лет 15, выступаю только как пользователь "своего" диалекта и пока вполне им доволен. Но это, конечно, не промышленное использование. Есть у Рефала-6 еще по-меньшей мере один активный пользователь - Игорь Щенков, и он время от времени высказывает претензии. Но к сожалению сил на них активно откликаться сейчас у меня очень мало. Се ля ви.
Когда я начинал программировать на Рефале, я начал с РЕФАЛа-5, пару программ написал на Рефале-6, а потом начал писать свой диалект J. Поэтому с идеями Рефала-6 знаком, программы читать могу, но программировать навыка нет, извините, что не стал вашей пользовательской базой. Кстати, завидую вам. Ваша пользовательская база по-меньшей мере в два раза больше моей J.
Здравствуйте, Василий!
Да, Рефал жив его пользователями. Но, если уйдёт поколение пользователей, то и Рефал умрёт. Может я и ошибаюсь, но по моим наблюдениям поколение воспроизводится как-то слабо (если вообще воспроизводится).
Спасибо за ссылки. Я их пока поверхностно посмотрел, думаю, потом изучу внимательнее. А мне наоборот, проще воспринимать языки с «современным» синтаксисом: РЕФАЛ-5, Рефал-6, Рефал Плюс. А вот читать Рефал-2 я ещё не привык — глаза не привыкли к k/F/что-то. Вашу реализацию постараюсь посмотреть в свободное время (видел, что исходник целиком написан в одном файле REFAL.REF, а внутри сплошной метакод B).
Кстати, студентов я пытаюсь привлекать к Рефалу. В частности, в моей реализации уже есть исходный код нескольких курсовых и дипломных проектов (в папке https://github.com/bmstu-iu9/simple-refal/tree/master/doc и её подпапках есть несколько записок по проектам, можно почитать). Но, к сожалению, их он не особо интересует (сдали и забыли).
--
Коновалов Александр Владимирович, преподаватель
кафедра ИУ9 «Теоретическая информатика и компьютерные технологии»
МГТУ имени Н. Э. Баумана, Москва
Доброе утро, Василий, доброе утро, господа!
Я, в свою очередь, посмотрел на ваш Рефал/2: исходники и интерпретатора, и компилятора. Довольно любопытная и минималистичная реализация, по всей видимости, ориентированная на написание каких-то небольших ежедневных скриптов, для которых часто под Unix используют sed, awk, Perl. Иначе нельзя объяснить, например, зашитую на уровне дизайна языка интерпретацию параметров командной строки. Сюда же: язык не предполагает никакую модульность — исходная программа должна писаться в виде единого файла — но для небольших скриптов этого вполне достаточно.
Стандартная библиотека языка выглядит так, как будто проектировалась именно для написания такого самоприменимого компилятора, иначе я не могу объяснить наличие буфера с произвольным доступом на запись (названного в документации загадочной формулой «X+LX»), куда запись производится по смещениям и который сохраняется в файл (третий аргумент интерпретатора) по завершении работы программы.
Интересной выглядит реализация поля зрения: вместо двусвязного списка узлов с полями «тег», «информация», «вперёд», «назад», используются четыре массива со смыслом данных полей (массивы «вперёд» и «назад» содержат не указатели, а целочисленные смещения). Массив «тегов» имеет символьный тип, что позволяет экономить память, которая терялась бы на выравнивание.
Язык сборки реализует систему команд, предложенную Романенко в диссертации 1978 года. По той же схеме, на сколько мне известно, реализованы «официальный» Рефал-2 Белоуса и Рефал-5.
Исходный код выглядит как написанный в прошлом веке, когда ещё не были распространены современные практики написания сопровождаемых программ. В коде на Си относительно часто используется goto (часть из них можно объяснить ручным дословным перекодированием псевдокода из диссертации Романенко), глобальные переменные с именем из одной буквы (часть из них тоже «переехала» из диссертации, например, Г1 и Г2). Впрочем, тем же страдают и исходные тексты Рефала-5 (я их когда-то внимательно изучал).
Исходник на Рефале выглядит как шифровка: многие функции названы по схеме «буква» + «число», иногда с суффиксом, названным по той же схеме (F20, F2E0, W15, F2E_S2…). Чтобы понять смысл каждой конкретной функции, приходится искать её место вызова (в других функциях) и пытаться понять, что же ей передаётся. Однако, если внимательно читать программу от начала и до конца, то понять целиком всё можно.
«Т.е. уже более 19 лет использования в практических задачах.»
Я им пользуюсь где-то с 2005 года, но больше в теоретических задачах: пишу для него компиляторы(ы). Просто сам язык для меня интересен. Также применяю его в (около)научной работе на кафедре. Например, чудовищно интересными были оптимизации совместного сопоставления с образцом (с выводом т.н. «глобального сложнейшего обобщения») и построения результата (используя алгоритм жадного строкового замощения, используемого обычно при поиске плагиата).
«За что я его (рефал) ценю?
0) удобно производить символьные преобразования»
Полностью согласен. Может заменить и Perl с AWK’ом, перемалывающие строки текста, и Lisp, преобразующий сложные символьные конструкции (например, в задачах компиляции).
«1) наглядность
Я использую как редактор far, раньше был Norton Commander.
Основной код программы на рефале (в метакоде B), которым занимаюсь при программировании (та часть,которую сейчас пишу), обычно помещается на экране (в окне редактора).
Не нужно никуда листать, смотреть и т.п.
В этом смысле, чем компактнее исходный код, тем лучше!!! А значит короткие односимвольные имена переменных предпочтительнее длинных-мнемонических. Чем меньше строк - тем нагляднее!!!!»
Я использую как редактор Far и Vim. Причём исторически начинал с Far’а (и мне не влом было набирать длинные имена переменных без автодополнения).
С тем, что чем компактнее исходный код, тем лучше, я не согласен. Я считаю, что чем легче читается и воспринимается, тем нагляднее. Например, что делает функция F2E_K? Для ответа на этот вопрос нужно прочитать её код, а также прочитать то, как она вызывается из других функций. А что делает функция PairResultBrackets? Имя нам подсказывает, что она спаривает скобки в результатном выражении. А значит, чтобы понять, что происходит в месте её вызова, тупо не надо читать другое место. Аналогична история с переменными. Что хранится в переменной T3? Нужно посмотреть, где эта функция вызывается и что по этому параметру передаёт. А что хранится в t.NextToken? Следующий токен.
«2) спецификации
В новых версиях есть внутренние функции. Безусловно, с их помощью можно спрограммировать и спецификации рефала/2 и еще много чего! НО!!!
Но они затуманивают алгоритм выполнения программы!!!
На самом деле, когда пишешь программу, каждый раз для себя доказываешь (теорему), что тот код, который пишешь, действительно решает ту задачу, которую надо решить...»
Вообще-то вложенные функции не могут заменить спецификации Рефала-2. Заменить спецификации можно только условиями Рефала-5 или более общей конструкцией — действиями с неуспехом Рефала-6, Рефала-Плюс, Рефала-7.
То, что они затуманивают выполнение программы, я не согласен. Они наоборот её проясняют. Например, функция Map, отображающая одну последовательность термов в другую:
Map SF TN ET = kSF TN. k/Map/ SF ET.
SF =
PlusOnes EX = k/Map/ /P1/ EX.
UnBracket EX = k/Map/ /UnBracket1/ EX.
UnBracket1 (EX) = EX
PutLines EX = k/Map/ /PutLine/ EX.
PutLine (EX) = k/P/ EX.
Здесь для примера функция PlusOnes прибавляет к каждому терму единицу, UnBracket, принимая список скобочных термов, все их разворачивает, PutLines распечатывает последовательность строк. Преимущество функции Map в том, что не надо описывать обработку всей последовательности — достаточно описать обработку одного элемента и сказать «сделай со всеми так».
Вообще, вложенные функции сейчас присутствуют практически во всех распространённых языках общего назначения: Си++ (с 2011 года), Java, C#, JavaScript, Python, Ruby, Scala, Kotlin, Rust, Go. Относительно Visual Basic не в курсе, не слежу за ним. И люди этим активно пользуются. Вложенные функции стали такой же общей практикой, что и ООП.
«3) привычка
Когда я смотрю на метакод B - я его читаю.
Когда ж "современный" синтаксис - приходится переводить... как с другого языка...
Особенно, когда несколько встроенных функций (как, например, в "декартовом произведении" п. 3.3.5.3 из
https://github.com/bmstu-iu9/simple-refal/blob/master/doc/manul.pdf )
Безусловно - красиво!
Но писать, думаю, быстрее проще, да и в случае возникновения ошибок, мне кажется, легче разбираться с простым кодом.»
Исключительно вопрос привычки и практики.
«4) возможность программной оптимизации не обязательна!»
Простой Рефал самоприменяется 10 секунд, поэтому оптимизация не лишняя. Кроме того, это очень интересно (писал уже выше).
Доброе утро, Василий!
«Другое - величина кода программы не более 32К»
Я писал большую программу на Рефале, которая не укладывалась 64 Кбайта для РЕФАЛа-5. Это компилятор Модульного Рефала (проект временно сдох):
https://github.com/Mazdaywik/mrefal
Он умеет компилировать программу из нескольких модулей, отслеживая зависимости между ними (при этом проверяется, что если модуль не менялся с момента последней перекомпиляции, и зависимые модули тоже не менялись, перекомпиляция не выполняется). Парсит свой входной диалект «Модульный Рефал», может порождать код на РЕФАЛе-5, Простом Рефале и Си++. Плюс строит таблицу перекрёстных связей между функциями. Такой вот комбайн. Ограничение на размер кода (при запуске версии, скомпилированной в РЕФАЛ-5) сейчас стоит в 300 Кбайт.
«Мне обычно трудно выразить смысл функции несколькими словами, которые можно записать в ее названии, а т.к. я не знаю англ., то мне это не мнемонично :(»
Ну это, скорее, вопрос стиля и привычки. Когда приходится писать на Базисном Рефале, то приходится вводить очень много промежуточных функций и приходится их именовать. Я обычно их именую так.
Если есть некоторая функция «Func», выполняющая роль точки входа в некий алгоритм, то служебные функции я как правило называю Func-Пояснение. «Пояснение» как-то определяется из контекста.
Если цель функции — вызвать другую функцию, а потом просто преобразовать её результат, то обычно называю такие функции как Func-Aux (auxiliary — вспомогательный).
Если вызывается промежуточная функция для выполнения цикла, то функция получает префикс Do: DoFunc (синтаксис циклов во многих языках программирования предполагает ключевое слово «do»).
Если цель функции — вызвать другую функцию и уже принять решение на основе её результата, то промежуточная функция получает префикс Sw: SwFunc или Func-SwПояснение (от слова switch — переключатель).
Вот пара примеров:
https://github.com/Mazdaywik/mrefal/blob/master/Sources/Compiler/Driver/MClusters.mref
https://github.com/Mazdaywik/mrefal/blob/master/Sources/Compiler/Driver/MCompiler.mref
Кстати, там есть пример функции с адовым числом суффиксов: «Synthesis-Recompile-Resolve-TablePrepared-Aux».
Вложенные безымянные функции избавляют от необходимости придумывать им имена. «Func-Aux» и «SwFunc» уходят за счёт того, что сразу можно написать вызываемую безымянную функцию, принимающую результат другой функции (см. функцию Fetch в руководстве). Do-функции уходят за счёт того, что многие циклы выражаются через функции Map и MapReduce (Map с состоянием).
Кстати, в вашем Рефале/2 в именах функций можно использовать любые непробельные символы (если быть точным, функции могут начинаться на любой символ, кроме «*» и не могут содержать знаки пробела и «/»). Так что по-русски тоже можно. J
Вложенные функции в Простом Рефале, кстати, являются синтаксическим сахаром. При компиляции они преобразовываются в обычные глобальные функции Базисного Рефала + операции связывания с заимствованными переменными. Об этом есть в немного устаревшей презентации по устройству Простого Рефала (сейчас устройство стало гораздо сложнее, но общие принципы в основе те же):
https://github.com/bmstu-iu9/simple-refal/blob/master/doc/historical/%D0%9A%D0%BE%D0%BC%D0%BF%D0%B8%D0%BB%D1%8F%D1%82%D0%BE%D1%80%20%D0%9F%D1%80%D0%BE%D1%81%D1%82%D0%BE%D0%B3%D0%BE%20%D0%A0%D0%B5%D1%84%D0%B0%D0%BB%D0%B0.pdf
В принципе, вложенные функции можно добавить и в Рефал-2 по аналогии с конструкцией «where» языка Хаскель (Хаскель своим синтаксисом немного близок к Рефалу-2). Правда, тогда синтаксис станет двумерным — нужно будет учитывать величину отступа в начале строки (сейчас только учитывается «пробел/не пробел» начале):
CartProd (EA) (EB) = k/Map/ /ByA/ EA.
WHERE ByA WA = k/Map/ /ByB/ EB.
WHERE ByB WB = (WA WB)
Вложенная функция ByA заимствует из своего окружения переменную EB (её нет в её левой части, но есть в правой). Функция ByB заимствует из своего окружения WA.
Вот реальный пример, как можно переписать программу с использованием WHERE-конструкции.
F4 SLSRST('('E2)E3=(/1/K/RDR//^СКОБ/.)K/F4/STK/P1/ST.K/P2/ST.E2.+
(/3/K/RDR//^УГР/.K/P1/ST.SR)K/F4/K/P1/ST.SRK/^T/.E3.
SLSRST('0'E2)E3=(K/F4OZ/K/RDR//^ЗНАЧО/.K/LENGW/E2..)+
K/F4/K/P1/ST.SRK/P2/ST.E3.
SLSRST('4'E2)E3=(/4/K/RDR//^ЗНАЧ/./4/K/F4D//0/E2.)+
K/F4/STSRK/P1/ST.E3.
SLSRST('2'E2)E3=(/2/K/RDR//^ЗНАЧ/./2/)(/0/E2)+
K/F4/STSRK/P1/ST.E3.
SLSRST('S'E2)E3=K/F4S/ST('S'E2)K/^ИД/..K/F4/STSRK/P1/ST.E3.
SLSRST('W'E2)E3=(K/F4W/K/P1/ST.('W'E2)K/^ИД/..)+
K/F4/K/P1/ST.SRK/P2/ST.E3.
SLSRST(S('EV')1E2)=K/F4E/K/P1/ST.(S1E2)K/^ИД/..K/^T/K/P2/ST..
SLSRST=(/1/K/RDR//^ПРОВ/.)K/^T/ST.
SLSRST(S('EV')1E2)E3=K/E/SLSRST(S1E2)E3.
F4 SLSRSTW2E3=k/SwW2/W2.
WHERE SwW2 ('('E2)=(/1/K/RDR//^СКОБ/.)+
K/F4/STK/P1/ST.K/P2/ST.E2.+
(/3/K/RDR//^УГР/.K/P1/ST.SR)+
K/F4/K/P1/ST.SRK/^T/.E3.
('0'E2)=(K/F4OZ/K/RDR//^ЗНАЧО/.K/LENGW/E2..)+
K/F4pair/.
('4'E2)=(/4/K/RDR//^ЗНАЧ/./4/K/F4D//0/E2.)+
K/F4sym/.
('2'E2)=(/2/K/RDR//^ЗНАЧ/./2/)(/0/E2)+
K/F4sym/.
('S'E2)=K/F4S/ST('S'E2)K/^ИД/..+
K/F4sym/.
('W'E2)=(K/F4W/K/P1/ST.('W'E2)K/^ИД/..)+
K/F4pair/.
(S('EV')1E2)=k/SwEmptyE/ E3.
WHERE SwEmptyE =K/F4E/K/P1/ST.(S1E2)K/^ИД/..K/^T/K/P2/ST..
E3=K/E/SLSRST(S1E2)E3.
F4sym =K/F4/STSRK/P1/ST.E3.
F4pair =K/F4/K/P1/ST.SRK/P2/ST.E3.
SLSRST=(/1/K/RDR//^ПРОВ/.)K/^T/ST.
Здесь функция SwW2 проверяет, чем является очередной терм, и, в зависимости от этого выбирает дальнейший путь. Функции F4sym и F4pair вызывают F4 для продолжения разбора остатка, увеличивая вершину стека ST на 1 или на 2. Но это, наверное, несколько надуманный пример.
Да, после введения этой where-конструкции Рефал-2 перестанет быть метакодом B. Вернее даже, вообще перестанет быть метакодом по определению метакода.
«Про оптимизацию кода на языке сборки. Мне показалось, что в реальных задачах время может сократиться процентов на 10... ради этого возиться показалось лишним...»
Не, ускорение, вроде, больше чем на 10 процентов суммарное. Но вообще оптимизация затеивалась только потому, что её делать было интересно.
Добрый день, Аркадий!
Большое спасибо за развёрнутый ответ. Сейчас я задам несколько развёрнутых вопросов. Заранее прощу прощения у подписчиков за многабукаф.
… а именно, с включением вложенных анонимных функций -- аналога лямбда-абстракции.…
Эта идея не моя, впервые она была предложена Сергеем Скоробогатовым в диалекте Refal-7 в 2006 году:
https://waybackmachine.org/web/20070813011422/http://iu9.bmstu.ru/science/refal.pdf
(даю ссылку на Wayback Machine, поскольку с сайта кафедры этот файл удалили). Вероятно, вы знакомы с ним (и со Скоробогатовым, и с диалектом), но ссылку я даю для всех читателей рассылки.
…(только поверхностно посмотрел доки)…
Основная дока (manul.pdf), к сожалению, (а) недописана, (б) частично неактуальна. Основной массив знаний (если углубляться) скрывается тут: https://github.com/bmstu-iu9/simple-refal/issues (включая закрытые задачи). Но на беглый просмотр там слишком много.
…было бы интересно и важно наработать множество примеров и техник с использованием этих новых средств, демонстрирующих их удобство и преимущество.
Пользуюсь вложенными функциями уже с 2009 года, мне нравится. Когда добавил их в язык, почувствовал прирост своей производительности, по сравнению с подмножеством Базисного Рефала. Пример их использования (несколько извращённый) вот:
https://github.com/bmstu-iu9/simple-refal/tree/master/doc/examples/inifile
Более нормальные примеры использования есть в исходных текстах самого компилятора.
Есть основания полагать, что Рефал создавался Турчиным именно как язык первого порядка, и в этом была интенция автора. … Рефал-5 -- это максимум того, что он был готов допустить.
Есть у меня идеи по статической типизации Рефала (но о них сейчас пока рано, через несколько месяцев будет в самый раз), и обдумывая их, я понял гений Турчина. РЕФАЛ-5 — это довольно компактное и выразительное подмножество, которое разрабатывалось не только и не столько для промышленного программирования (хотя писать большие программы на нём можно — SCP4), сколько для автоматического анализа. Поэтому и язык первого порядка, и нет неуспехов (но есть условия), и замкнутый набор встроенных функций. Мои идеи по выводу и проверке типов хорошо ложатся на РЕФАЛ-5, в то время как с функциями высшего порядка, неуспехами и сторонними функциями всё не так очевидно.
А в чём согласование с интенцией первого порядка таких особенностей, как выражения с двусторонним доступом, концепция поля зрения и динамическая типизация? Мне это немного не очевидно. И что подразумевается под явной активацией (мне не знаком этот термин)?
Вообще, я недавно начал смотреть лекции Абрамова по метавычислениям и задумался: а каким должен быть промышленный инструмент программиста для выполнения метавычислений? Подумалось, что это должен быть язык программирования и компилятор к нему, с хорошей поддержкой рефлексии (возможность как разобрать в рантайме AST любой функции, так и наоборот, её построить из AST и тут же вызвать), с возможностью интерпретации во время компиляции (как в C++14 constexpr), с возможностью сдампить набор функций в готовый для выполнения модуль. И если в такой среде реализовать специализатор как обычную библиотечную функцию, то вполне реализуются все три проекции Футамуры-Турчина. А потом вспомнил «пятикнижие Турчина» — описанный там язык и описанная там среда вполне подходят под это описание, разве что constexpr там нету.
Функциональные объекты этим условиям не удовлетворяют, они эктенсиональны: функцию можно вызвать, но нельзя препарировать, рассмотреть ее устройство.
Да, кстати. У меня (и у Скоробогатова в Refal’е-7) функциональные объекты сопоставляются с s-переменными.
Но конечно, это не значит, что последователи не вправе делать по-своему, с опорой на новые знания и опыт. И может быть, особенности Рефала дадут возможность по новому увидеть и понять функциональные объекты.
Я разрабатывал (и разрабатываю) диалект Рефал-5λ (который сначала назывался Простой Рефал), ориентированный на промышленное программирование. Вложенные функции повышают продуктивность программиста — они есть. Необходимо расширение возможностей языка — библиотека функций не закрыта, можно написать свою функцию на Си++. Даже больше. Есть удобный синтаксис для написания таких функций — вставки кода на Си++ (https://github.com/bmstu-iu9/simple-refal/issues/11, https://github.com/bmstu-iu9/simple-refal/blob/master/src/srlib/Library.sref). Т.е. не надо править и менять интерпретатор для написания ещё одной примитивной функции. Сейчас я работаю над унификацией с РЕФАЛом-5 (планирую сделать точное надмножество).
Что касается меня, как приложившего руку к созданию Рефала-6, могу только сказать, что в последние годы, уже лет 15, выступаю только как пользователь "своего" диалекта и пока вполне им доволен. Но это, конечно, не промышленное использование. Есть у Рефала-6 еще по-меньшей мере один активный пользователь - Игорь Щенков, и он время от времени высказывает претензии. Но к сожалению сил на них активно откликаться сейчас у меня очень мало. Се ля ви.
Когда я начинал программировать на Рефале, я начал с РЕФАЛа-5, пару программ написал на Рефале-6, а потом начал писать свой диалект J. Поэтому с идеями Рефала-6 знаком, программы читать могу, но программировать навыка нет, извините, что не стал вашей пользовательской базой. Кстати, завидую вам. Ваша пользовательская база по-меньшей мере в два раза больше моей J.
Добрый вечер, Аркадий!
Большое спасибо за ответы на мои вопросы.
«Насколько я понял, там у него вложенные именованные функции, а у Вас только анонимные, да? И отсутствие именованных для случая рекурсивных функций вы компенсируете использованием комбинатора Y. Это действительно очень круто и интересно.»
У меня только анонимные, и это, отчасти, вытекает из ограничения промежуточного представления. Используется классическая списковая реализация (как в Рефале-2 или Рефале-5), а замыкания реализованы как объекты (кольца двусвязного списка) со счётчиком связей. Некоторое представление можно извлечь из этой презентации (некоторые детали в ней уже устарели, но общая идеология осталась прежней), последние два слайда:
https://github.com/bmstu-iu9/simple-refal/blob/master/doc/historical/%D0%9A%D0%BE%D0%BC%D0%BF%D0%B8%D0%BB%D1%8F%D1%82%D0%BE%D1%80%20%D0%9F%D1%80%D0%BE%D1%81%D1%82%D0%BE%D0%B3%D0%BE%20%D0%A0%D0%B5%D1%84%D0%B0%D0%BB%D0%B0.pdf
Именованные вложенные функции позволят программисту писать рекурсивные и взаиморекурсивные функции, которые смогут возвращать ссылку на себя (своё имя, которое тоже будет замыканием). Например:
Foo {
t.A t.B
= $func F { = &G; 0 = t.A }
$func G { = &F; 0 = t.B }
: s.X s.Y = s.X;
}
Bar {
1 = <Foo 'xy'> : s.1 = <Eq s.1 <<s.1>>>;
2 = <Foo 'xy'> <Foo 'ab'> : s.1 s.2 = <Eq s.1 s.2>;
}
Eq {
s.A s.A = True;
s.A s.B = False;
}
С моей точки зрения вызов <Bar 1> должен возвращать True, поскольку в функции Eq сравнивается с самим собой один и тот же объект. В то же время <Bar 2> обязательно должен возвращать False, поскольку сравниваются на равенство два разных объекта, созданных в разное время, по разному себя ведущих.
Вот. Реализовать именованные взаиморекурсивные функции с подобными требованиями к равенству экземпляров замыкания и при этом поверх списковой реализации со счётчиком ссылок я не придумал как. Если разрешать циклические связи во время выполнения, то уже нужно делать сборку мусора. Технически, конечно, не сложно (я писал сборщики мусора), но как-то не хочется вводить лишних сущностей. Поэтому для простоты реализации только анонимные.
Комбинатором Y я не компенсирую. Я его когда-то давно написал из любопытства и положил в библиотеку, но с тех пор не пользовался. Компенсирую я функциями Map и MapReduce (ну, иногда — Reduce) — они реально заменяют циклы. А там, где их не достаточно, использую обычную рекурсию.
«Это интересно. Когда-то я тоже думал о выводе статитческих типов типов, но до реализации руки не дошли.»
У меня два студента должны опробовать два подхода статической типизации РЕФАЛа-5: один на курсовом, второй на дипломе. Если у них что-то получится, то напишу в рассылку. Кстати, РЕФАЛ-5 идеальный для этого модельный объект (о чём писал в прошлом письме).
«Да, это неочевидная, может даже спорная мысль. Я имел в виду, что в соответствие с интенцией первого порядка хорошо, когда все объекты могут иметь образы на бумаге, а еще лучше, когда объект можно отождествить с его образом. А на бумаге мы обычно пишем тексты, которые имеют начало и конец, могут содержать вложенные структуры, то есть быть деревом. И обрабатывать такие структуры уместно с любого конца. А когда всякий объект тождествен своему текстовому структурному образу, естественно говорить о динамической типизации, а не о статической (статический тип это что-то дополнительное к объекту, а динамический он тут же в образе представлен). Концепция поля зрения - это полный образ всей переменной части состояния рефал машины, для обычных языков это стек фреймов или даже что-то более сложное и не имеющее явного "бумажного" образа.
Явная активация это такая нотация, при которой вызов функции синтаксически отличается от терма, содержащего символ функции и параметры, и который можно активировать позже. Насколько я понимаю, этим рефал отличается от лиспа, где терм (F x y) в одном контексте интерпретируется как вызов функции F, а в другом (под Quote) как пассивный список из трех элементов. Явная активация в каком-то смысле тоже "вытекает" из принципа тождественности объекта его образу (что вижу, то и имею, ничего лишнего или подразумеваемого).»
Большое спасибо, теперь всё стало на свои места.
«Да, надо "всего лишь" уметь извлекать функции из поля памяти в метакоде и класть обратно. Между прочим, в рефале-6 для этого почти все есть, надо только чуток модифицировать (и перекомпилировать) компилятор (чтобы представление в метакоде оставалось где-то в конце выражения, являющегося кодом языка сборки). Проблема лишь в том, чтобы договориться о формате метакода, а на это не хватило духу.»
Или другой вариант: написать декомпилятор языка сборки «на лету». Я как-то изучал формат языка сборки РЕФАЛа-5 методом обратного инжиниринга (о диссертации Романенко я не знал, в исходники не лез из спортивного интереса), и мне показалось, что реконструировать исходный текст по нему было бы не сложно. До декомпилятора, правда, руки не дошли.
«Да, наверно это правильно. А функция TYPE их как-то выделяет?»
Не, не выделяет. И это скорее бага, чем фича (хотя в документации на стр. 21 об этом написано). Создал баг: https://github.com/bmstu-iu9/simple-refal/issues/103. Хотя там по факту добавить одну ветку в switch: https://github.com/bmstu-iu9/simple-refal/blob/master/src/srlib/Library.sref#L934-L979.
«А замыкание, это наверно уже не символ, а скобочный терм, да?»
Не, символ. Символ содержит указатель на кольцевой список, содержащий указатель на глобальную функцию и набор связанных переменных. Об этом есть в презентации по ссылке выше на страницах 6, 11–13, 35–36. В принципе, их можно рассматривать и как своего рода скобочный терм, который, однако, нельзя расщепить образцом.
«И в пользу коротких есть следующий аргумент: Рефал, в отличии от классических императивных языков является двумерным: предложение естественно пишется в строку, а группа предложений в столбец.»
Соглашусь, что это вопрос вкуса. Я, например, принципиально избегаю вертикального выравнивания в своих программах. Основная причина: низкая сопровождаемость. Если приходится изменить текст в одной из «ячеек», и при этом он оказывается шире ширины столбца, то приходится подгонять строки выше и ниже. Или, например, массово переименовал какое-нибудь имя (идентификатор или имя функции) и все «таблицы», где он был, поехали. Во-вторых, я предпочитаю ограничивать длину строки 80 символами (удобно бить окно редактора по вертикали пополам), а выравнивание текста в виде таблицы часто приводит к длинным строкам, причём, если длинную строку разбить на две, то теряется красота таблицы.
Но, опять-таки, это дело вкуса.
Кроме того, я никогда не программировал с использованием коротких имён переменных, всегда писал длинные. Обсуждение с Василием Стеллецким и вами натолкнуло на мысль, что мой стиль может быть не идеальным. В частности, вполне, оказывается, можно читать программы на Рефале-2 с односимвольными именами. Надо будет попробовать писать иначе.
--
Александр Коновалов aka Маздайщик
Лет семь назад встретил цитаты из великих (не помню кто и цитата по
памяти, не точный текст):С тей пор как прочитал, как и отрезало ;-)
Согласен, там понятнее. Я просто хотел проиллюстрировать, что в Алголе-68 есть синтаксис для цикла, условие которого проверяется в середине. И для этого _дословно_ переписал алгоритм, предложенный Бойко на Си (у него был бесконечный цикл с единственным выходом по break в середине). Т.е. исходный алгоритм на Си на пару писем раньше тоже был запутанным.