LINUX.ORG.RU

Какой бы синтаксис придумать для функции?

 ,


0

3

Предыстория: я делаю парсер который можно быстро перенастроить на какой-нибудь другой язык. Парсер умеет в типы, и проблемы различить

int y=foo(100);
int x(y);

от

typedef int y;
int x(y);

проблемы как таковой нету но ощущается ощущение некрасоты и необходимости очень много тестировать, особенно когда это всё будет прикручиваться к редактору: там в силу требований быстро подсказывать и подсвечивать возникает много нюансов. И один из них это то, что парсер где-то должен вычислять является ли буковка типом или ссылкой на переменную. это не проблема была бы, если бы не нужно при этом было учитывать ситуацию когда объявление типа находится ниже по тексту. В общем гемор.

Упрощением было бы всегда указывать явно что перед нами функция: тогда можно было предварительно распарсить редактируемую строку, оставив «висячие» ссылки на типы которые будут ниже по тексту.

но для этого нужен хороший красивый синтаксис, например:

int func x(y z)
int fn x(y z)

парсится идеально но выглядит вырвиглазно. конечно можно сделать тип опциональным, но вдруг есть менее вырвиглазные варианты? например

function(int) x(y z) //тип явный
function x(y z) //тип по тому что возвращают

сделать

function x(y z) -> int

можно но блин ну насколько же это мразотно выглядит. как будто пробежал окамловец и насрал. сделать можно сразу все варианты кстати.

может я чего-то простого не понимаю, и есть красивый вариант который мне в голову не приходит? что-то покрасивее вышеописанного?



Последнее исправление: ckotctvo (всего исправлений: 2)
❴Function❵┅━━━╍⊶⊰Int x ❃•❃ Int y⊱⊷╍━━━┅❴Float❵
◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤

                  Тело функции
            Выравнивание кода по центру

                 ╭═────═⌘═────═╮
                   Return value
                 ╰═────═⌘═────═╯

◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤
LINUX-ORG-RU ★★★★★
()

Всего то нужно сделать, чтобы тип был в конце объявления (см. Kotlin, Rust, TypeScript, Python, Pascal и другие).

function foo(x: Int, y: Int): Int

или

function foo(x: Int, y: Int) -> Int

Ну и ещё может быть автовывод/any-тип, тогда тип не указывается.

Сразу исчезает неоднозначность и больше не нужно знать все типы, чтобы корректно распарсить файл.

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

KivApple ★★★★★
()
Последнее исправление: KivApple (всего исправлений: 4)
Ответ на: комментарий от KivApple

имя переменной продиктовано алгоритмом, а тип уже деталями реализации,

Тип переменной продиктован алгоритмом, а имя - детали даже не реализации, а оформления.

firkax ★★★★★
()

парсер для человека, а не человек для парсера.
не пиши очередное парсероговно типа fn, даже если потрудиться придется подольше и побольше.

с надеждой на лучшее

olelookoe ★★★
()

может я чего-то простого не понимаю, и есть красивый вариант который мне в голову не приходит? что-то покрасивее вышеописанного?

нету.

когда синтаксис отдельной сущности приходится выдумывать, то нету. Без идеалогии получатся заплатки, нашлёпки и костыли.

любое решение будет как «как будто пробежал окамловец и насрал» ;-)

MKuznetsov ★★★★★
()

Этих синтаксисов — в каждом языке свой. Заимствуйте какой нравится. Неужто они все до одного плохи?

ugoday ★★★★★
()
Ответ на: комментарий от ugoday

Он боится сделать как в расте, но говно как в си тоже не хочет. Муки.

unC0Rr ★★★★★
()
Ответ на: комментарий от KivApple

Ну и ещё может быть автовывод/any-тип, тогда тип не указывается.

Автовывод типов в заголовке функции в большинстве случаев доставляет больше проблем, чем удобства.

С точки зрения компилятора, он не сможет понять сигнатуру функции полностью не разобрав ее тело. При этом этот процесс рекурсивный: если в теле функции встретится другая неизвестная функция, то нужно будет оставливаться и ждать пока не будет разобрана та вложенная функция.

С точки зрения программиста тоже код и сложнее читать и при написании будет хуже работать автовывод типов локальных переменны внутри функции.

Грубо говоря, если у тебя четко определено, что функция возвращает двухбайтовое число, то в функции ты сможешь написать в теле функции return 14 и будет однозначно понятно, что за тип этого числа 14, без всяких уточнений и хардкожения какого-то дефолтного типа int

Darfin
()
Ответ на: комментарий от Darfin

вообще спасибо за подсказку, это реально баг в парсере оказался. он зацикливается если функция A дергает B, а B дергает A.

ну скажем так, цель номер один это сделать парсер verilog, system verilog и переводчик их в «нормальный язык», похожий на с++. к верилогам у меня плохое отношение из-за begin/end, уродской модели выполнения и самое главное невозможности указать штатными средствами на расположение элементов или приказать использовать hvt-транзистор например.

для этой задачи можно обойтись и каким нибудь generic типом, как в жабаскрипте.

пока склоняюсь к function(type) name(args….) как наименее уродскому варианту. за совет спасибо, будем думать в направлении duck types как их ввести на случай если тип не указан.

ckotctvo
() автор топика
Ответ на: комментарий от unsigned

Какие у человеков проблемы с fn? Мне нравится, я человек. Лучше только fun.

fun - «прибежал окамлист и насрал» же..

ЗЫ. Сам немного окамлист...

AndreyKl ★★★★★
()
Ответ на: комментарий от AndreyKl

Споры о синтаксисе - это, кажется, всегда споры сишников, паскалистов и ML-щиков.

И только лисперы молча усмехаются.

unsigned ★★★★
()

Предыстория: я делаю парсер который можно

так как мы не в курсе деталей что вы там делаете (ссылок,описаний,обоснований,идей не изложено) можем просто фантазировать...

int name : function(arg1 arg arg) {...} // или что-то в этом роде

в принципе логичнее, идентификатор name связывается с изначально анонимной функцией.

у функций имя это вообще говоря опция :-)

Заодно не придётся ещё одну тему заводить «придумать синтаксис для лямбды»

MKuznetsov ★★★★★
()
Последнее исправление: MKuznetsov (всего исправлений: 1)

Большая проблема сишной нотации в том, что она плохо грепается. Я могу сделать grep -rE '^(def |fn |fun |function)' и найти всё что хотел без всяких парсеров. В случае си я так сделать не могу. Зато сэкономили немного символов, можем ведь!

Ещё есть принцип наименьшей неожиданности. Никто не хочет спирально расшифровывать трёхэтажный указатель наподобие void (*signal(int, void (*fp)(int)))(int);. Ну да, снова сэкономили символы, да, при определённой сноровке можно научиться это читать, но зачем? Лучше сделать более деревянно и глупо, и не тратить на это время тысяч пользователей, набирают текст они всё равно быстрее чем думают. Как-нибудь через набор типов всю эту конструкцию выразят, не обломаются.

Третий момент – это сложность набора на конкретной раскладке. К примеру в кириллический язык добавить @ # $ & – это прям больно.

Все остальные критерии типа вырвиглазно, мразотно, насрал окамловец, похоже на мой нелюбимый язык – на самом деле не волнуют почти никого.

neumond ★★
()
Ответ на: комментарий от neumond

Большая проблема сишной нотации в том, что она плохо грепается.

// grep-friendly С-formating:
int
foo(void *,...);

имя функции начинается с новой строки, то что между началом строки и ( это её имя, то что строкой выше тип результата, между скобками параметры

MKuznetsov ★★★★★
()

является ли буковка типом или ссылкой на переменную

И в каких ситуациях это можно перепутать?

ya-betmen ★★★★★
()

Для начала стоит заметить, что множество языков синтаксически стали похожи друг на друга:

большинство языков придерживаются устоявшейся парадигме ООП, и соотвественно позволяют использовать общепринятые паттерны программирования.

TechnoMag ★★★
()
Последнее исправление: TechnoMag (всего исправлений: 1)

вдруг есть менее вырвиглазные варианты

что-то покрасивее вышеописанного?

(fn func [x] ...) — идеально.

Nervous ★★★★★
()
Последнее исправление: Nervous (всего исправлений: 1)
Ответ на: комментарий от unsigned

И только лисперы молча усмехаются.

)))))))))))))))))))))))))))))))))))))

Bfgeshka ★★★★★
()

Делай научный синтаксис как в школе и институте записывают. Пример: func double_x : int (x : int) => x * 2, или func double_x (x int) int = {x * 2}

LongLiveUbuntu ★★★★★
()
Ответ на: комментарий от unsigned

Объективно, когда в лисповых функциях прописываешь декларации типов, выглядит вообще некрасиво и неудобно. По счастию, не предполагается делать это слишком часто.

ugoday ★★★★★
()
Ответ на: комментарий от TechnoMag

Для начала стоит заметить, что множество языков синтаксически стали похожи друг на друга

Есть 2 основных кластера - сиподобные, которые делали для людей, и паскалеподобные, которые делали для парсеров. Собственно если ты хочешь чтобы людям было удобно твой код читать/писать смотри в стороны сиподобных, если тебе важнее один раз описать лл-грамматику для однопроходного парсера, то выбор тоже очевиден.

Конечно есть ещё лиспо-питоны, но эти ковырялки мало кого интересуют в разрезе программирования.

ya-betmen ★★★★★
()
Ответ на: комментарий от ya-betmen

В ситуации когда объявление буковки ниже по тексту или что хуже когда в режиме редактора буковку начинает зависеть от контекста. Надо в реалтайме успеть распарсить. А с спецсловом можно отложить выведение типа на потом

ckotctvo
() автор топика
Ответ на: комментарий от ckotctvo

При чём тут выше/ниже? Покажи мне контекст в котором переменная и тип неразличимы. Может конечно у тебя тип через равно объявляется, типа type_a_alias = type_a; но это ты сам себе злобный буратино если такую фигню прдумал.

ya-betmen ★★★★★
()
Последнее исправление: ya-betmen (всего исправлений: 1)
Ответ на: комментарий от TechnoMag

Паскаль, (условно) го, руст, да хоть тупоскрипт. По сути большинство новых языков такие, ибо декларативная грамматика, чтобы парсер не писать. А человеки как нибудь переживут.

ya-betmen ★★★★★
()
Ответ на: комментарий от TechnoMag

Назовите хотя бы три паскалеподобных.

Verilog, ST, easylang ; каждый для своей области «must known», не знать его нельзя

MKuznetsov ★★★★★
()
Ответ на: комментарий от unC0Rr

Да, долой плюсы, пишем на чистом Си, в нём эта конструкция вполне однозначна.

Хотя, за y может скрываться макрос, включающий в себя и закрывающую круглую скобку, и точку с запятой, и ещё что-то, но это уже другая тема.

firkax ★★★★★
()
Ответ на: комментарий от MKuznetsov

у функций имя это вообще говоря опция

У переменных - тоже, если речь про Си. Хотя я такое не одобряю (и про переменные и про функции).

firkax ★★★★★
()
Последнее исправление: firkax (всего исправлений: 1)
Ответ на: комментарий от neumond

Я могу сделать grep -rE '^(def |fn |fun |function)' и найти всё что хотел без всяких парсеров.

А что ты «хотел» найти то? Список функций? Зачем он? Если список публичных функций то в хорошем Си-коде это grep -F extern.

сэкономили

Замусоривать место на экране и правда ни к чему. Программист из-за этого тратит время на двигание глазами и, хуже того, на листание вверх-вниз.

firkax ★★★★★
()
Ответ на: комментарий от ya-betmen

Парсеры Паскаля и Си мало чем отличаются по сложности реализации (оба простые).

firkax ★★★★★
()
Ответ на: комментарий от ya-betmen

А как же фортран? Он ни к одному из этих двух не относится.

Про «паскаль для парсеров» не согласен.

если тебе важнее один раз описать лл-грамматику для однопроходного парсера

Что за лл-грамматика? Никто формальными грамматиками исходники не парсит (ну кроме каких-то модных современных возможно). А насчёт однопроходности парсера - так они все однопроходные, где ты видел другие то, а главное - зачем? Некоторые даже не только парсят за 1 проход а ещё и компилируют сразу.

firkax ★★★★★
()
Последнее исправление: firkax (всего исправлений: 1)
Ответ на: комментарий от firkax

Для паскаля - возможно, не вникал, а так я очевидно про формальное описание синтаксиса, чтобы разбирать его каким-нить бизоном, проблнма в том, что оно не умеет в контекстнозависимый разбор, что и рождает все эти fn, fun, funct, function и прочий визуальный мусор. Очевидно, что сам парсер может читать всё в один проход, но я о возможности например использовать функцию до её декларации, как это технически сделано - неважно.

ya-betmen ★★★★★
()
Ответ на: комментарий от ya-betmen

Никогда не понимал эту тягу к бизонам. Его в нормальных местах и так и так для парсинга исходников не используют.

А при чём тут использование функции до объявления? В Си, в начале его существования, это было нормой, но потом поняли что это плохая идея и надо хотя прототип писать заранее. В Паскале сразу используется второй вариант, без прототипа нельзя. Парсинг и компиляция и там и там могут быть сделаны однопроходными, отличие Си такое: его авторы посчитали допустимым при обнаружении неизвестной функции, во-первых, эту функцию вписывать в таблицу идентификаторов как функцию, во-вторых дефолтить её тип в int и передавать аргументы неким самым простым способом в надежде что окажется так и надо. Итог этих угадываний оказался плохим - провоцирование багов в программах, теперь все компиляторы пишут на такое варнинг, хоть и вынуждены поддерживать для совместимости с древним кодом. В Паскале сразу решили что не надо. Можно было бы сделать два прохода чтобы находить прототип со всеми типами даже если он ниже чем использование, но в целом этот вариант не посчитали удобным. Обращу внимание, всё вышеописанное с парсером не связано. Сам по себе вызов функции парсится одинаково вне зависимости от того, известна ли она к этому моменту или нет, в обоих языках.

Ещё можно привести в пример пхп: до какой-то версии там был однопроходный компилятор без построения синтаксического дерева. Функции, объявленные где-то внизу, вызывать можно, безо всяких проблем. Отличие от Си/Паскаля в том что пхп это скрипты, соглашений о передаче аргументов там не требуется, угадывать ничего не надо, соответственно при обнаружении вызова функции он просто компилируется в соответствующий байт-код с указанием её имени, а объявлена она или нет - важно только когда до этого места дойдёт уже выполнение кода.

firkax ★★★★★
()
Последнее исправление: firkax (всего исправлений: 1)
Ответ на: комментарий от firkax

Вообще не согласен.

Вот, допустим, пишешь банальный подсчёт суммы элементов массива. Сразу очевидно, что переменную назовешь sum. А вот тип данных выбирается уже потом - надо посмотреть какой тип у элементов массива, надо подумать не нужно ли взять тип шире из-за риска переполнения и т. д.

Вызываешь функцию и присваиваешь её переменной - ты быстрее сообразишь как это обозвать (если функция названа понятно, плюс есть всякие универсальные имена типа result, temp и т д), а тип возврата придётся подсматривать в объявлении функции или подсказках IDE.

Реализуешь какой-нибудь алгоритм по псевдокоду - имена переменных там есть, а типов нет (потому что в каждом языке свои типы). Опять надо думать и принимать решения.

Есть языки без указания типов вообще (можно дискутировать о них, но они существуют и успешно применяются и входят в топы языков), а реальных промышленных языков без указания имен переменных нет.

Имя переменной можно придумать ещё на высоком уровне продумывания алгоритма «берём эту фигню, запоминаем, суммируем, умножаем», оно уже существует даже для алгоритма на словах. А тип появится уже при реальном кодинге. Типы разные в разных языках, зависят от других частей программы, используемых библиотек и т. д.

Имя переменной отвечает на вопрос «что» (именно на уровне семантики, «число», «строка» это ерунда, настоящее «что» это «номер дома», «количество итераций», «время с момента старта системы» и т. д.), а тип переменной лишь на вопрос «как». Обычно отталикаются от «что», а уже потом думают «как».

Ты когда выходишь из дома у тебя обычно уже есть в голове зачем ты идёшь (в магазин, в гости, в парк и т д), а уже потом решаешь как (пешком, на автобусе и т д). Наоборот, конечно, бывает (у меня безлимитный проездной на автобус, куда бы поехать на нем?), но гораздо реже.

KivApple ★★★★★
()
Последнее исправление: KivApple (всего исправлений: 5)
Ответ на: комментарий от KivApple

Кто же пишет суммирование не зная что он собрался суммировать? Это разве что с++-ный шаблон если. А так, у тебя уже есть какие-то числа, ты знаешь откуда они взялись и что означают, и вот ты дошёл до того, что их требуется просуммировать.

Если переводишь с другого языка, на котором уже всё написано и нет типов, и переводишь не с начала а с середины - может быть.

firkax ★★★★★
()
Ответ на: комментарий от firkax

Массив может быть обьявлен выше по коду, ты можешь помнить «там числа», но конкретное int там или long надо посмотреть. А ещё надо подумать не может ли быть переполнение. Например, если это массив char, то суммировать мы в 90% случаев будем в int, потому что ну слишком уж быстро переполнится (но есть исключения).

Но чтобы выбирать тип и рассуждать так, мы уже должны решить, что в переменную поместим сумму. А значит можно назвать её sum.

Не выйдет рассуждать о типе переменной не держа в голове её назначение. Будет сумма - будем думать о ширине и переполнении, будет минимум/максимум - будем думать о начальном значении (может быть optional взять?) и т д.

Но если мы знаем назначение переменной, то мы уже можем дать ей имя, а над типом думать на секунду позже (заодно имея напоминалку, что мы собираемся хранить, в виде уже написанного имени).

KivApple ★★★★★
()
Последнее исправление: KivApple (всего исправлений: 2)
Ответ на: комментарий от firkax

Никогда не понимал эту тягу к бизонам. Его в нормальных местах и так и так для парсинга исходников не используют.

Большинство современных парсят парсерами.

ya-betmen ★★★★★
()
Ответ на: комментарий от KivApple

Ладно, может быть у всех по-разному. У меня такого не происходит. Тип это важная штука, за ним надо следить и int с long путать нельзя. И ставить наобум «суммируем int значит результат int» тоже нельзя, надо сразу запланировать какое количество каких слагаемых там допустимо, проверить что какой-то внешний код гарантирует что этот лимит превышен не будет либо добавить проверку в сумматор, решить нужна ли нам арифметическая сумма или сумма по модулю 2^32 (или ещё какому) и исходя из этого назначить типы. Имя же переменной может быть каким угодно, иногда вообще не связанным с её содержимым (разные имена из 1 или 2 букв).

firkax ★★★★★
()
Последнее исправление: firkax (всего исправлений: 1)

К слову, а какой у вас синтаксис объявления переменных? Думаю, от этого плясать надо, чтобы унификация достигалась.

ugoday ★★★★★
()
Ответ на: комментарий от unsigned

Споры о синтаксисе - это, кажется, всегда споры сишников, паскалистов и ML-щиков.

И только лисперы молча усмехаются.

Так у лисперов и синтаксиса то особо нет.. не о чем спорить..

Споры о зубах и стоматологах - это практически всегда споры тех кому нужны либо коронки либо пломбы, либо, может ортодонты чтобы зубы выровнять. И только лисперы беззубые молча усмехаются..

AndreyKl ★★★★★
()

полевые испытания на раёне показали, что ключевых слов nah и jopta в принципе достаточно чтобы выразить практически любую мысль.
зачем в ЯП тащить чуждые нашему народу fn и прочее говно не понятно.

olelookoe ★★★
()

Короче, автор, я всё придумал:

  1. Вводим синтаксис для объявлений
varName [type] ≜ expr

где тип переменной можно опускать, если он очевиден.

  1. Функции определяем точно так же
funcName [type] ≜ λ arg1, arg2 type, arg3 type {
// body
}

теперь тип означает тип возвращаемого значения. Задача решена.

ugoday ★★★★★
()
Для того чтобы оставить комментарий войдите или зарегистрируйтесь.