Pull to refresh

Comments 20

Не совсем понятны критерии "простоты" языка. Вот классический Паскаль - простой?

А Фортран (ранних версий, 4-й, или 77-й)?

Ну и примеры... Человеку, незнакомому с языком далеко не сразу очевидно что делает вот это:

/// для каждого i в списке выводим на экран i+1
pub fn print_all_plus_one(l: List(Int)) {
  // этот пример надуманный;  как правило, приходится использовать всего один цикл
  //один цикл:
  let res = {
    use i <- list.map(l)
    int.to_string(i + 1)
  }
  // другой цикл:
  use s <- list.each(res)
  io.print(s)
}

Что такое use? Зачем оно? Как устроен "другой цикл"? Где он начинается и где заканчивается?

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

Человек ещё заодно считает, что эти 8 строк проще, чем функция высшего порядка (map +1 list).

Вдогонку. Про простоту языка.

Простой язык - это то, по коду которого сразу понятно как он будет работать? Или это язык где все сложные конструкции скрываются в одной строке кода?

Вот попробуйте на С (к примеру) без внешних библиотек расписать что делает этот самый цикл

  let res = {
    use i <- list.map(l)
    int.to_string(i + 1)
  }

Крайне поверхностная статья. Многие пункты - заслуга не языка, а экосистемы. Рассуждения про типы просто нелепые. В Си слабая типизация, Питон с аннотациями мало чем отличается. Хаскелл, конечно, же сложный язык, но вовсе не из-за "случайных последовательностей символов", и ещё там не только лямбды.

Статью нейросеть писала?

C и его последователи довольно хреновые языки, с двумя другими не сталкивался но сомневаюсь в "простоте".

В любом случае сложность библиотек куда важнее сложности языка

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

Есть языки, которые безо всяких сторонних библиотек позволяют эффективно решать поставленные задачи, а есть в которых это так просто не получится.

Уже долгое время самым простым языком среди не-эзотерических является Оберон ревизии 2013. Дедушка Вирт на склоне лет знал толк в простоте.

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

А какими еще языками приходилось пользоваться? С чем сраниваете?

Про шарп ничего не могу сказать - не приходилось на нем писать. Просто интересно.

C, C++, Perl, Basic, Pl/1, Fortran, assembler-s-ы, Java, Rubi

Из тех что вы описали Шарп действительно один из самых приятных. Но думаю что подавляющее большинство интерпретируемых языков будет проще шарпа. В них просто меньше базовых конструкций. Руби в их семействе скорее белая ворона в плане подходов.

Статья хороша. Жалко что это просто репост чужого (перевод).

Самый простой язык это BASIC. Чтобы никаких указателей, лябд и прочей ереси, только человекочитаемые слова. Ну, может быть Pascal ещё. А C, Go и вот этот ещё какой-то, не знаю про него ничего, точно не простые.

Ну тут надо четко обозначить что понимать под "простотой".

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

Ко мне вот сейчас иногда заходят с вопросами по С (С/С++ у нас используются мало, это не "основные" языки, но иногда они удобнее для решения некоторых задач).

Так вот чтобы человек, никогда с указателями не работавший, быстро в тему въехал, объясняю так:

  • Открываешь шкаф, ищешь коробку на которой написано "сахар" - это обращение к переменной по ее имени - var

  • Открываешь шкаф, там на третьей полке вторая коробка справа - это обращение по указателю - *var

Оба подхода дадут одинаковый результат - получишь коробку с сахаром. Это, пожалуй, основное что вызывает сложности в С у начинающих.

Если же говорить с точки зрения синтаксиса, то что бейсик, что паскаль (классический), что С достаточно просты - смотришь код и видишь алгоритм. И знаешь что вот эта строка кода не скрывает под собой никаких тайн. В отличии от:

  let res = {
    use i <- list.map(l)
    int.to_string(i + 1)
  }

тут возникает куча вопросов - какой тип у res? Сколько он в памяти занимает? Что скрывается за use i <- list.map(l)? Сколько времени оно будет работать? Будут ли с памятью динамически манипулировть? Там внутри цикл или что? А что такое i? Что в нем будет храниться?

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

то что бейсик, что паскаль (классический), что С достаточно просты - смотришь код и видишь алгоритм.

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

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

С Си-подобными указателями возникает некоторая путаница в предмете указания (указатель на массив не отличается от указателя на его первый элемент). Это делает аналогию с коробкой неполной. Скорее, тут правильнее было бы говорить о позиции в шкафу. Но мне кажется, что в данном случае проще объяснить сразу через машинный адрес.

С Си-подобными указателями возникает некоторая путаница в предмете указания (указатель на массив не отличается от указателя на его первый элемент).

В целом - да. Это тоже часть идеологии С - есть адрес блока памяти, а помнить что там лежит уже на совести разработчика.

В этом плане еще проще реализовано в RPG (с которым работаю последние 6+ лет).

Там указатель - это просто адрес в памяти. Он не типизирован. Точнее, есть два типа - указатель на данные - pointer, получается вызовом %addr и указатель на процедуру - pointer(*proc) - получается вызовом %paddr().

Адресной арифметики, как в С, нет. Т.е. можно делать инкремент/декремент адреса, но всегда на заданное количество байт.

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

dcl-s var1 char(100);             // строка 100 символов
dcl-s var2 char(100) based(ptr);  // строка 100 символов по указателю ptr

// прежде чем использовать var2 нужно инициализировать ptr
ptr = %addr(var1);

// теперь var2 и var1 суть одно и то же
// или можно аллоцировать память динамически
ptr = %alloc(100);

// но потом не забыть деаллоцировать
dealloc ptr;

Никто не запрещает связывать с одним указателем несколько переменных разных типов. Вся ответственность за последствия на совести разработчика.

А в остальном - очень простой процедурный паскалеподобный (с легким привкусом PL/I в синтаксисе) язык для эффективного решения определенного класса задач (работа с БД и коммерческие вычисления). И очень самодостаточный - в языке для решения этих задач есть все что нужно. Никаких дополнительных библиотек в 90% случаев вам не потребуется. Все типы данных что есть в БД (decimal/numeric, char/varchar, date/timw/timestamp...), работа с датам, временем, строками, арифметика с фиксированной точкой (в т.ч. и операции с округлением) - все в языке. Работа с таблицами индексами - хоть напрямую, хоть SQL непосредственно в код встраивай.

В последнее время расширили работу с массивами - цикл перебора for-each, проверка вхождения в массив in, разбиение строки на составляющие по разделителю %split, склейка в строку %concat и %concatarr. Есть динамические массивы. Есть поиск в массиве %lookup (и модификации типа %lookupge, %lookuple и т.п.), причем, если массив объявлен как сортированный, с модификаторами ascend/descend, то используется двоичный поиск, иначе - перебором по массиву.

В последних версиях добавили перезагрузку процедур

// Отправка сообщения в queLIFO/queFIFO очередь
// Возвращает количество отправленных байт
// в случае ошибки -1
dcl-pr USRQ_SendMsg int(10) overload(USRQ_Send: USRQ_SendKey);

dcl-pr USRQ_Send int(10) extproc(*CWIDEN : 'USRQ_Send') ;
  hQueue    int(10)                    value;                                  // handle объекта (возвращается USRQ_Connect)
  pBuffer   char(64000)                options(*varsize);                      // Буфер для отправки
  nBuffLen  int(10)                    value;                                  // Количество байт для отправки
  Error     char(37)                   options(*omit);                         // Ошибка
end-pr;

// Отправка сообщения в queKeyd очередь
// Возвращает количество отправленных байт
// в случае ошибки -1

dcl-pr USRQ_SendKey int(10) extproc(*CWIDEN : 'USRQ_SendKey') ;
  hQueue    int(10)                    value;                                  // handle объекта (возвращается USRQ_Connect)
  pBuffer   char(64000)                options(*varsize);                      // Буфер для отправки
  nBuffLen  int(10)                    value;                                  // Количество байт для отправки
  pKey      char(256)                  const;                                  // Значение ключа сообщения
  nKeyLen   int(10)                    value;                                  // Фактический размер ключа
  Error     char(37)                   options(*omit);                         // Ошибка
end-pr;

Не вдаваясь в подробности - USRQ_Send и USRQ_SendKey - две реальных процедуры с одинаковым типом возвращаемого значения, но работающих по разному и с разным набором параметров. А USRQ_SendMsg - универсальная объединяющая обертка. Ее используем в коде, а компилятор уже подставит вызов нужной процедуры в зависимости от того с каким набором параметров оно вызывается.

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

Пример:

dcl-ds t_dsCPDL qualified template; // расшифровка срока хранения лога
  Units    char(1);                   // единицы (D/W/M/Y)
  Count    char(3);                   // количество
end-ds;

dcl-ds dsCPOCTL len(100) qualified;
  poSts  char(1);                 // Статус:
                                  //   • N – неактивна
                                  //   • Y – активна
                                  //   • O – вызов RRUCHK#HD
  poCPDL    char(4);              // Срок хранения истории в формате XNNN, где:
                                  //   • X – фромат срока (D – день, M – месяц, Y – год)
                                  //   • NNN – числовое значение указанного срока
  poRESA    char(10);             // Коды результатов на авторизацию
  poRESW    char(10);             // Коды результатов на ожидание
  poLogL    char(1);              // Режим логирования:
                                  //   • 0 - ошибки
                                  //   • 1 - +инфо
                                  //   • 2 - +отладка
  poMsgQ    char(10);             // Очередь для вывода сообщений
  /////////////////// переопредления ///////////////////
  dsCPDL    likeds(t_dsCPDL) samepos(poCPDL);
  ResA      char(1) dim(10)  samepos(poRESA);
  ResW      char(1) dim(10)  samepos(poRESW);
end-ds;

template тут - аналог typedef. Т.е. просто описание, без создания самой переменной. Переменная в данном случае описывается как like (для обычной переменной) или likeds для структуры

Модификаторы samepos - это как раз перекрытия. Т.е. обращаясь к dsCPOCTL.poCPDL мы работаем со строкой 4 символа (вида D002, M001 и т.п.), а обращаясь к dsCPOCTL.dsCPDL - уже с ней же, но в виде структуры из элементов dsCPOCTL.dsCPDL.Units и dsCPOCTL.dsCPDL.Count. Аналогично - dsCPOCTL.poRESA - строка из 10-ти символов, а dsCPOCTL.ResA она же, но в виде массива из 10-ти отдельных символов.

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

Это делает аналогию с коробкой неполной.

А аналогия с коробкой просто чтобы понять суть - все равно как вы описываете способ доступа к области памяти где лежат данные - по имени переменной или по адресу этого блока в хранилище. И в реальной жизни, если вы привыкли все держать на своих местах, заглядывая в шкаф за сахаром вы сразу будете смотреть "по указателю" - на третью полку, вторая коробка справа. Потому что привыкли что там лежит сахар. И если кто-то положил туда коробку с солью, то можете схватить соль вместо сахара (хорошо если проверите прежде чем в кофе сыпать :-)

А вот в незнакомом шкафу - да, будете по имени искать, просматривая все коробки в поисках той, на которой написано "сахар".

В мире есть два языка программирования - ассемблер и все остальные.

Первый - сложный. Остальные учить можно хоть всем сразу, было бы время

Я думаю, любой документированный набор инструкций проца проще плюсов.

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

Sign up to leave a comment.