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

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

а теперь заканчивайте

Слежу за статистикой. Результаты неутешительные. Только треть проголосовавших за качественные материалы на Хабре… :-( Еще один довод в копилку, что Хабр стал "не торт". Хотя мы можем попробовать все еще все изменить :-)

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

Думаю, что мне не влом написать в личку автору ) Но это уже прям вопиющие случаи, когда некоторые авторы генерируют тонны не самого качественного контента. Информационный шум, так сказать. Можете посмотреть — таких авторов есть десяток здесь. Как будто — «репутация ничто — копирайтинг все»

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

А ведь всего то достаточно выделить момент с ошибкой и нажать Ctrl+Enter. Даже никуда ненадо в прфоиль лесть и тд.

Я в курсе, спасибо, кеп.
Только эта функция не работает дважды на этой странице (можете проверить) — это до сих пор пофиксить не могут, только после рефреша.

Если вы про то, что пропадает кнопка «отправить» — отправляйте вторым нажатием ctrl+enter (проверено, работает). Я всегда так делал и глюк с пропадающей кнопкой заметил сильно не сразу. :)
Кстати, в бета-версии хабра кнопка не пропадает. Но там и ctrl+enter не работает на отправку.
Ни разу не сталкивался с тем чтоб не работало.

ну, вот я такой везучий

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

Правильно читать "приведу ничем не подтвержденные аргументы".

Ну давайте я агрумент приведу, хоть я об этом и писал совсем недавно в соседней теме. Я считаю, что питон для начинающих — отравленный подарок, поскольку его правила трудоформализуемы для начинающих. В итоге как только размер программы у начинающего превышает пару сотен строк, начинается борьба с неочевидными (для начинающего) глюками.

Примеры:
1) variable scope: видимость переменных на чтение и на запись разная

вот этот код напечатает 10 10
i=10
def foo():
    print(i)
foo()
print(i)


вот этот код напечатает 2 10
i=10
def foo():
    i = 2
    print(i)
foo()
print(i)


а вот этот откажется компилироваться
i=10
def foo():
    print(i)
    i = 2
foo()
print(i)



2) списки и ссылки на них
вот этот код напечатает [13, 0]
a = [0]*2
a[0] = 13
print(a)


а вот этот НЕ напечатает ожидаемые [[13, 0],[0,0]]
b = [[0]*2]*2
b[0][0] = 13


А всё потому, что двумерный массив нужно декларировать не так
b = [[0] * 2 for i in range(2)]
b[0][0] = 13



И даже когда начинающий поймёт, где баг, и скопирует со стэковерфлоу [[0] * 2 for i in range(2)] вместо [[0]*2]*2, он вряд ли поймёт, почему это так.

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

В итоге получается картина, схожая с вот этой карикатурой

C интуитивнее?

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

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

Чтобы выбор был информированным, рядом неплохо бы иметь сопоставимый список слабых мест C.

список слабых мест C.

Указатели

Управление памятью

Никаких exception, упали значит упали ;)

Строки, хнык, хнык

Никаких встроенных списков и словарей, все вручную

Никакого разнообразия встроенных модулей, libc хватает всем.

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

Ну а разнообразие модулей для первого знакомства с программированием, конечно, полезно, но не сильно обязательно. Хотя обвинить си в малом количестве библиотек это сильно :)

Хотя обвинить си в малом количестве библиотек это сильно :)

Библиотеки в С не являются частью языка. Их много, да.

Ну какой-нибудь matplotlib или numpy тоже отдельно устанавливать надо.

Околонулевые средства обобщения кода (параметрический полиморфизм, higher-order functions, вот это всё).
Околонулевая типобезопасность.
Обильно посыпанный UB стандарт.

Учли ли вы контекст дискуссии, и приводите ли слабые места языка при знакомстве с программированием?

Да.


Средства обобщения — так им же тоже учить надо, и все они важны на практике. Например, ряд вещей часто становится легче, если язык вам позволяет легко и непринуждённо абстрагировать части алгоритма в виде передаваемых извне функций. В C для этого надо приседать с указателями на функции (у которых достаточно своеобразный синтаксис), с void*, со временем жизни всего этого счастья, и так далее.


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


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

Видимо, мы с вами по-разному понимаем знакомство с программированием. Для меня знакомство — это программы по пять строчек. Через некоторое время пятьдесят. Указатели на функции приходят гораздо позднее. Ну и про UB — не так страшен чёрт, как его малюют. Поймать UB на описанном мной уровне не так просто (нет, я понимаю, что написать i++ + ++i; можно и в одну строчку).

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

Да, похоже на то.


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

Этап пятистрочных программ проходит очень, очень быстро, практически за пару занятий.

Ох. Вы уверены? Знаете, в нашем провинциальном вузе (кстати, учат на питоне), за весь первый семестр нет ни одной программы длиннее пятидесяти строк.

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

Ну так 50 ≫ 5 вообще-то :)

Для меня знакомство — это программы по пять строчек. Через некоторое время пятьдесят.

В своём опыте? Да, уверен. За полгода люди в 7-8 классе точно начинали писать код сильно длиннее 5, 10, 50 и даже 100 строк.


А вот с основной массой студентов всё сложнее.

Дело даже не в этом. Зачем давать язык, на который вы даже в рамках программы не делаете долговременную ставку, и преимущества которого целевая аудитория оценить и осознать заведомо не может?

Надо лишь убедиться в том, что опыт не избирательный. Все ли ваши одноклассники писали код длиннее ста строк в седьмом-восьмом классе?

Ну а языков нужно давать в профильном вузе сильно больше одного, и C/C++ рано списывать со счетов, поэтому про ставки трудно сказать. Ещё раз напоминаю, не предлагаю выбирать си первым языком. Первое знакомство imho должно происходить с программируемым калькулятором.
Надо лишь убедиться в том, что опыт не избирательный. Все ли ваши одноклассники писали код длиннее ста строк в седьмом-восьмом классе?

Нет, не все, но достаточно значимая часть, чтобы, ИМХО, не заметать их под ковёр из идущих самостоятельно впереди программы. Но это, впрочем, субъективщина.


Ну а языков нужно давать в профильном вузе сильно больше одного, и C/C++ рано списывать со счетов, поэтому про ставки трудно сказать.

Плюсы как раз хороший выбор, и, ИМХО, их если и давать, то до/вместо C. Иначе люди потом часто пишут на плюсах как на С с классами.

Начинайте с турбопаскаля! Отличный язык для реализации алгоритмов школьного уровня! С с классами или плюсы — это уже профориентация.
Кстати, сейчас в школах есть что-то типо русского паскаля. Среда называется КуМир (да, да, не смейтесь). Позволяет писать простенькие алгоритмы, рисовать, работать с массивами и строками. К ЕГЭ многих на этой штуке готовят. И вот знаете, довольно неплохо выглядит и, мне кажется, этого достаточно для изучения таких понятий как алгоритм, переменная, ветвление, цикл, массив.
Spoiler header
image

С или С++ — это оверхэд в задаче изучении алгоритмической базы.

Ни одно решение на традиционном паскале нельзя позаимствовать. Может оно и правильно, пусть руками пишут, а не с интернетов копируют.

Я сам не щупал этот Кумир, но народ хвалит.

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


Мне вот совершенно неочевидно, что оно там нужно.

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

Тогда ничему не мешает, условно, преподавать полгода питона, полгода плюсов, полгода хаскеля, полгода пролога. Для всех — ознакомиться с самим фактом существования таких вещей достаточно (а глубина и не нужна — та же школьная алгебра предельно неглубока по сравнению с соответствующей математикой), а там, авось, кто-то и заинтересуется.


Более того, все эти разговоры о том, что лучше, можно будет подкреплять числами: в части школ, например, сначала рассказываем про питон, потом про плюсы, потом про хаскель, потом про пролог, в другой части рассматриваем эти языки в другом порядке, и так далее. Получаем 24 варианта школ, которые уже можно численно сравнивать — откуда больше людей пошли в индустрию (или хотя бы в околопрограммерский вуз) и успешно там задержались, откуда больше людей успешно выступают на олимпиадах, етц.


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

Тогда надо преподавать то, с чем, с большей вероятностью, встретится будущий ученик. Хотя что там будет через 10 лет — поди предскажи. Преподавать сегодня Julia или нет?

Тогда ничему не мешает, условно, преподавать полгода питона, полгода плюсов, полгода хаскеля, полгода пролога
едва ли в школьную программу влезет хотя бы пара языков
Тогда надо преподавать то, с чем, с большей вероятностью, встретится будущий ученик. Хотя что там будет через 10 лет — поди предскажи
10 лет назад было понятно, что программисты нужны. Более того, судя по популярности буткемпов, даже если сейчас спрос начнет падать, университеты всё равно еще долго его не закроют
В последнее время как-то слишком много говорят о хаскеле. А вот в документации написано, что и в питон, и в хаскел отступы обязательны. Это противоречит понятию «текстовый формат данных», и не только является примером дурного тона, но и опасным источником ошибок. Изучать такие языки первыми точно не стоит.

Си, лисп и пролог в некотором смысле образуют базис — то есть это максимально разные подходы к программированию, у которых минимум общего. Остальные более-менее раскладываются по этому базису и от себя добавляют лишь небольшое количество уникальных особенностей. Вот эти три вещи и надо преподавать в общеобразовательных школах. А остальное по специальности.
Это противоречит понятию «текстовый формат данных», и не только является примером дурного тона, но и опасным источником ошибок. Изучать такие языки первыми точно не стоит.

Я много пишу на хаскеле, но не встречал случаев, когда ошибочные отступы привели бы к компилирующемуся, но неработающему коду. Сконструировать их, наверное, можно, но они будут очень искусственными.


Как я понимаю, основная разница с питоном в том, что у вас в питоне может быть как


if foo:
    bar
    if baz:
        quux
else:
    meh

так и


if foo:
    bar
    if baz:
        quux
    else:
        meh

тогда как в хаскеле у вас любой if должен быть завершён else.


Можно, конечно, забыть оператор после вложенной do-нотации, но так можно оператор и после закрывающей скобки в C случайно поставить.


Си, лисп и пролог в некотором смысле образуют базис — то есть это максимально разные подходы к программированию, у которых минимум общего.

Знаете эту шутку про лисп со стороны сишника и со стороны хаскелиста?

Да, похоже это только питоновская проблема.
А что не так с лисп?
А что не так с лисп?

Когда программист на Си слышит про Лисп, он уточняет — «а, это тот функциональный язык?»
Когда хаскелист слышит про Лисп, он уточняет — «а, это тот императивный язык?»

Тогда однозначно включаем хаскелл в школьную программу вместо лиспа. Этот язык развивает чувство юмора. Ещё его можно использовать в паре с си: на хаскелле описывать, что нужно вычислить, а на си описывать элементарные алгоритмы, которыми будет оперировать вычислитель хаскела.
Ещё его можно использовать в паре с си: на хаскелле описывать, что нужно вычислить, а на си описывать элементарные алгоритмы, которыми будет оперировать вычислитель хаскела.

Зачем так?

Потому что хорошо, когда каждый язык решает свой круг задач и не претендует на универсальность.
Вы можете определять сложение как:
instance Num Nat where
  (+) a Zero = a 
  (+) a (Succ b) = Succ(a+b)

но вряд ли вас устроит такое определения для сложения 734211040 и 610489189. Впрочем, и оно ссылается на сложение, определённое в строенном типе. А его иначе как
(+) a b = ({
  long ADD;
  register long _a asm("rdi") = a
  register long _b asm("rsi") = b
  asm("mov rax,rdi\nadd rax,rsi":"=a" (ADD));
  ADD;})

и не определить.

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

Ну, во-первых, в хаскеле так определять сложение особого смысла нет — выразительной системы языка не хватит, чтобы что-то интересное про Пеано потом доказывать, а зачем оно ещё нужно, я сходу не могу придумать.
Во-вторых, трансформация упомянутого вами сложения вот в этот вот асм — она ж врёт. Для data Nat = Z | S Nat и очевидного сложения выполняется теорема ∀ n → n < S n, для упомянутого вами сложения — нет (либо для него не выполняется тотальность сложения, что тоже не очень приятно).
В-третьих, получается, что С тоже язык такой, неполноценный, так как изнутри языка там + вообще никак не определишь.


Ещё можно начать обсуждать вопросы реализации, скажем, аналога gmp на хаскеле (или идрисе) с доказательством (в случае идриса) эквивалентности операций на числах Пеано и вот этом вот gmp, но это уведёт нас сильно не туда.

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

Нашёл пример интеграции хаскел и си:
github.com/mgrabmueller/rdtsc

А теперь вопрос: можно ли считать, что любая функция в хаскел либо имеет определение на хаскел, либо импортирована подобным образом? Если да, то это в точности то, о чём я писал в предыдущем комментарии, и хотелось бы посмотреть, каким образом из GMP сделали Integer. Если нет, то странно, и непонятно, где проходит граница.

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

Нет, нельзя так считать.


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

Касательно оптимизации. Поигрался с кодом:
prime :: (Integral a) => a -> Bool
prime 1 = True
prime x = and [ x `mod` y /= 0 | y <- [2..(x-1)] ]

Выглядит красиво, не поспорить,
а теперь запускаем:
> prime(1999993)
True
it :: Prelude.Bool
(0.85 secs, 816,057,504 bytes)

> prime(3997859)
True
it :: Prelude.Bool
(1.64 secs, 1,631,185,144 bytes)

> prime x = and [ x `mod` y /= 0 | y < — [2..(div (x-1) 2)] ]
prime :: Integral a => a -> Prelude.Bool
(0.01 secs, 0 bytes)

> prime(1999993)
True
it :: Prelude.Bool
(0.41 secs, 408,056,872 bytes)

> prime(3997859)
True
it :: Prelude.Bool
(0.81 secs, 815,620,928 bytes)

Я, конечно, не ждал чуда с y^2, но на /2 от платформы, занимающей 800МБ на диске расчитывал. Но ладно.
Код на си:
int try_div(int a,int b)
{
	return a%b;
}

int test_prime(int arg)
{
	if(arg<2) return -1;
	if(2==arg) return 0;
	if(!(arg%2)) return 2;
	for(int i=3;i<arg;i+=1)
		if(!(try_div(arg,i)))
			return i;
	return 0;
}

int main(int argc,char**argv)
{
	int j;
	for(int i=3997850;i<3997870;i++){
		j=test_prime(i);
		WriteI(1,i);
		WriteC(1,"\t");
		WriteI(1,j);
		WriteC(1,"\n");}
	return 0;
}


Результат 0.01 сек
3997850 2
3997851 3
3997852 2
3997853 29
3997854 2
3997855 5
3997856 2
3997857 3
3997858 2
3997859 0
3997860 2
3997861 7
3997862 2
3997863 3
3997864 2
3997865 5
3997866 2
3997867 47
3997868 2
3997869 3

Process returned 0 (0x0) execution time: 0.010 s

Алгоритм точно такой же наивный с полным перебором. Но в 160 раз быстрее. Так что оговореннная архитектура наносит какие-то ограничения, но и без неё у хаскел оптимизатор упирается в намного более серьёзные ограничения. Он ещё 1.6 ГБ памяти потратил. Даже 4 млн по 8 байтному bool — всего 32 МБ промежуточных данных. Честно, нет идей, откуда такие большие числа.
но и без неё у хаскел оптимизатор упирается в намного более серьёзные ограничения

Да: например, такое ограничение, что для того, чтобы оптимизатор работал, его неплохо бы запускать. Вы запускали код из репла, там никакой оптимизации (и компиляции) нет.


Кроме того, вы зачем-то написали функцию, принимающую произвольный Integral a, тогда как ваша сишная версия не имеет никакого рантайм-полиморфизма, а вместо этого работает только с интами. Либо наворачивайте и в ней полиморфизм, либо пишите явно Int и в хаскель-версии.


Кстати, я гарантирую, что если вы сделаете в сишной версии аналог того, что вы сделали с Integral, типа такого (сорян за возможно кривой синтаксис указателей на функции):


struct IntegralOps
{
    void* (*add) (void*, void*);
    void* (*sub) (void*, void*);
    ...
    void* (*mod) (void*, void*);
};

void* tryDiv(void *a, void *b, IntegralOps *ops)
{
    return (*ops->mod)(a, b);
}

// остальные функции так же

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


А пока давайте напишем более прямой аналог:


prime :: Int -> Bool
prime x = and [ x `mod` y /= 0 | y <- [2..(x-1)] ]

main :: IO ()
main = mapM_ (\n -> print (prime n, n)) [3997850..3997869]

(отдельный случай для x = 1 не нужен, and [] ~ True), скомпилируем:


% ghc -O2 Main.hs

и запустим:


% /usr/bin/time ./Main -A16k
[...]
0.04user 0.00system 0:00.04elapsed 100%CPU (0avgtext+0avgdata 3280maxresident)k
0inputs+0outputs (0major+181minor)pagefaults 0swaps

Жрёт на 0.01 с больше, чем сишная версия на моей машине, памяти — ну плюс-минус сколько там рантайм вначале выделил на хип, столько и сожрало. Судя по внутренней статистике, сам код выделил меньше сотни килобайт, и GC был только один раз, в конце работы:


          96,248 bytes allocated in the heap
           3,312 bytes copied during GC
          44,408 bytes maximum residency (1 sample(s))
          25,224 bytes maximum slop
               2 MiB total memory in use (0 MB lost due to fragmentation)

                                     Tot time (elapsed)  Avg pause  Max pause
  Gen  0         0 colls,     0 par    0.000s   0.000s     0.0000s    0.0000s
  Gen  1         1 colls,     0 par    0.000s   0.000s     0.0001s    0.0001s

Погодите, а разве в том примере есть полиморфизм в рантайме? Вроде же компилятор должен всё раскрыть...

Зависит от оптимизаций. Если запускать в репле или компилять с -O0, оно не раскроется. Или, для полноты картины, если оно в другом модуле будет написано с NOINLINE-прагмой, тогда тоже ничего не раскроется.


Если же оптимизации таки включить, то тогда да, компилятор либо заинлайнит функцию (и подставит значение словаря для Integral a), либо просто сделает её специализацию для интов. Ну, вернее, если оставить код как есть, то для Integer'ов, так как по умолчанию тип численных литералов — Integer, а не Int, поэтому тип списка выводится как [Integer]. Поэтому надо будет добавить аннотацию к списку, например, так:


prime :: Integral a => a -> Bool
prime x = and [ x `mod` y /= 0 | y <- [2..(x-1)] ]

main :: IO ()
main = mapM_ (\n -> print (prime n, n)) [3997850..3997869 :: Int]

Тогда с оптимизациями скорость будет точно такой же, как у мономорфной версии prime.


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

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

Не думаю, что там рядом предлагают складывать «734211040 и 610489189».


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


Нашёл пример интеграции хаскел и си:
github.com/mgrabmueller/rdtsc

Можно пример интеграции Си и конкретно rdtsc, к слову? Там у вас тоже будет либо FFI (в асм), либо компиляторный интринсик.


А теперь вопрос: можно ли считать, что любая функция в хаскел либо имеет определение на хаскел, либо импортирована подобным образом?

Нет. Почитайте haskell report — это, конечно, не ISO-стандарт, но семантика там описывается достаточно неплохо.

На мой взгляд, основная цель школьного программирования примерно такая же, как и от школьной биологии, химии, социологии, экономики и т.д. — дать представление и некоторые самые базовые навыки в области, которая может им пригодиться. Тем кому понравится — пойдут в старших классах/университете на нужное направление. Кому не понравится — забудут, как я химию, например. Поэтому нужно давать базовые навыки в компьютере (чтобы дисплей с процессором не путали), базовые навыки в работе с ОС (чтобы браузер с текстовым редактором не поняли), базовые понятия: программа, интернет, и, подобно задачам по химии — задачи по алгоритмам (переменные, циклы, вот это вот всё). И вот на алгоритмы и программки в 5-50 строк может уйти год обучения спокойно. Хотя бы потому, что такие понятия не очень интуитивны.

зачем вообще нужно преподавание программирования в обычной среднестатистической школе

Меня когда-то впечатлил вот этот ted talk - https://www.youtube.com/watch?v=60OVlfAUPJg (транскрипт - https://ted2srt.org/talks/conrad_wolfram_teaching_kids_real_math_with_computers), tldw: если изначально интегрировать компьютер в процесс обучения (в данном случае - в процесс обучения математики), то можно достичь намного более интересных результатов, чем сейчас. Действительно, подружить школьников с wolfram/matlab/etc, а не заставлять 11 лет учить, как решать очередной вид уравнений на бумаге - мне кажется намного полезнее с практической точки зрения.
Наверное (экстаполирую), практическую часть любого другого STEM-предмета можно преподавать не как "читаем учебник, решаем задачки в тетрадке", а как "читаем учебник, пишем программы, которые что-то считают, моделируют или визуализируют". В таком процессе обучения, программирование (в каком-то виде), понадобится изучать весьма рано, и программировать нужно будет много и часто. Но от воплощения в реальность все эти мечты, конечно, довольно далеки.

И да, и нет. Да — действительно, пускай пользуются и осваивают. Нет — должен быть достаточно прочный фундамент, чтобы человеку позволить пользоваться благами цивилизации. Знаете, люди уже настолько разленились, что не могут двухзначные числа складывать в уме. Это вызывает не то что боль, а полный ступор. То же и с более сложными материями — те же производные. Я знаю алгоритм как их брать, вероятно возьму любую. Но как только я умею это делать — не зазорно пользоваться тем же Матлабом (мне, кстати, больше Maple нравился), чтобы перепроверять свой результат. Именно так работает обучение. Иначе будет… Как пример.
Девочка 27 лет. У нее apple Watch. С цифровым дисплеем. И она жалуется — как вы пользуетесь аналоговым циферблатом. Де, родители меня не научили, думали, что в школе научат, а в школе уже думали, что все это знают — как по стрелкам определять время. А признаться в незнании было стыдно… И это реальный уровень современного поколения. Куда мир катится :-/ Зато вот игрушки на айфоны ставят с закрытыми глазами (сужу по своим малолетним детям)

Чем больше всего в школе, тем лучше! Вот у меня была информатика в школе, крутые преподы из военного училища, в начале вообще были i386 и дос, потом олимпиады, тусовки, внеклассные занятия. Было классно! Турбопаскаль, ООП, оверлеи, память, изучали и делились знаниями друг с другом. Интернета тогда не было!
Начинал с турбопаскаля, потом перешёл на С. Не вижу ничего плохого, если бы сразу начал с С.
Это вы уже наверное забыли приколы с форматным вводом, с памятью, с массивами.
В Паскале все это вводится постепенно исходя из назначения. В Си — сразу и исходя из реализации.
Да банальный scanf требует задания форматной строки и ссылки на переменную.
Для седьмого класса средней школы С был всё же мутноват.

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

А какого возраста дети? Сколько часов занятий у них в неделю, есть ли домашние задания, и если есть, то какого объёма в часах? Понятно, что это посчитать сложно, но, наверно, есть приблизительные оценки?

И можно ли где-то посмотреть программу обучения (мне это очень интересно)?
Никаких exception, упали значит упали ;)
судя по опыту с++ (где половина проектов запрещает исключения) и парадигме дизайна раста, исключения не являются неоспоримым достоинством языка. А если брать всё множество языков, то это топ1 инструмент для заметания ошибок под ковер.

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

В том-то и дело, что чтобы говорить об отказе от исключений, надо обсуждать Раст с must_use и оператором ? — или Хаскель с монадами и HKT.


В языке Си обработка ошибок устроена ещё хуже чем позволяют исключения.

must_use это по сути аналог аттрибута warn_unused_result и его аналогов. Но я говорил в первую очередь про то, что работа с ошибками в си явная, в отличие от неявных по природе своей исключений.
что работа с ошибками в си явная

Не могу согласиться с этим тезисом. Потому что на уровне Си Вы можете заигнорить все коды ошибок и никто этого не узнаёт при компиляции. Только если самому глазками смотреть код и в документацию на используемые функции

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

в С нет исключений, так что неактуально.
в С++ — вроде были способы это пофиксить, но тут лучше 0xd34df00d подскажет

в С++ — вроде были способы это пофиксить, но тут лучше 0xd34df00d подскажет
тут ключевое слово «были» — спецификатор throw в с++11 депрекейтнули, а к с++17 удалили из стандарта. Самое явное что можно сделать — навесить noexcept(false), что по сути является значением по умолчанию

Ну, я не говорю, что С++ идеал. Но вот в Растра ты же пишешь Option? Значит, ты уже заранее знаешь какие варианты exception может кинуть конкретная функция??

только не Option а Result, это я и назвал явностью — возможность увидеть все типы ошибок из объявления функции

Тут ещё нужен сахар для того, чтобы с этим всем удобно было работать. В расте он, как я понимаю, сделан в виде частных случаев в парсере для Result и тому подобного. В хаскеле — это просто частный случай монадической структуры Either. В плюсах нет первого, а второе делать нереально (разве что, что-то накостылять на корутинах, но там возникают свои проблемы).

Во‐первых, warn_unused_result в стандарте нет. И в заголовочных файлах libc я его что‐то тоже не особо вижу. Не знаю, как в других libc, но ag -i unused $(qlist glibc | grep include) нашёл мне кучу комментариев, несколько идентификаторов, определение атрибута и целых две функции, которые им всё же аннотировали.


Во‐вторых, если функция в случае ошибки возвращает NULL, а без неё — указатель на что‐то, то warn_unused_result ни разу не поможет: вы же результат используете! А то, что fwrite не может записать строку в NULL, полученный от провалившегося fopen компилятор не знает. (Вообще, есть атрибут nonnull, но он вроде не заставит вас доказать компилятору, что вы не передаёте NULL в функцию, он просто не даст его молча передать в явном виде.) Ошибки, возвращаемые в виде специальных значений используемого типа ещё хуже: под них даже атрибутов нет.

Во‐первых, warn_unused_result в стандарте нет
это такая же категория компилятора языка С, как и категории документации компилятора rust (стандарта то у него нет)
Во‐вторых, если функция в случае ошибки возвращает NULL, а без неё — указатель на что‐то, то warn_unused_result ни разу не поможет: вы же результат используете!
тут вы конечно правы. Но я всё еще утверждаю не то, с чем вы спорите
это такая же категория компилятора языка С, как и категории документации компилятора rust (стандарта то у него нет)

извините, но это чушь. Почему? Потому что Си — это промышленный язык и он стандартизирован. Возможно, это пережитки прошлого и на текущем этапе развития нам не нужные специальные стандарты ISO/МЭК и прочие на ЯП. Но Rust/Python etc. являются стандартом де-факто. Т.е. референсный компилятор + референсная оригинальная документация. На ней нет штампа "стандарт". И она меняется согласно времени. Но поведение компилятора версии от вчерашней даты детерминированно и это всех устраивает :-/

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

Во-первых, «референсная оригинальная документация» (как будто бывает фальшивая документация) написана для пользователей, а не для разработчиков компиляторов. Она может быть недостаточно точной в определении требуемого от компилятора поведения.
Во-вторых, если rustc и mrustc расходятся в поведении, кто из них будет прав? Правильный ответ — никто, ведь нет однозначных требований, которым должна удовлетворять каждая из реализаций.

Да, для языка с одним «кошерным» компилятором и одним подражателем такое положение дел может казаться нормальным, но это только до тех пор, пока мы не вспоминаем что rustc это лишь один из многочисленных фронтендов llvm, гарантии миддленда которой «сильно коррелируют» с требованиями стандартов си/плюсов…
кажется, вы не понимаете, зачем нужен стандарт.

я-то понимаю. И я знаю к чему приводит излишняя фанатичность. Вы же помните — сколько было стандартов С/C++ со всеми их расширениями и все равно компиляторы реализуют что-то отличное?


Во-вторых, если rustc и mrustc расходятся в поведении, кто из них будет прав? Правильный ответ — никто, ведь нет однозначных требований, которым должна удовлетворять каждая из реализаций.

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

Вы же помните — сколько было стандартов С/C++ со всеми их расширениями и все равно компиляторы реализуют что-то отличное?
тем не менее компиляторы стремятся к соответствию стандарту. Стандарта нет — каждый делает как хочет и забивает на остальных, а пользователи пытаются обходить все эти различия, как с джаваскриптом в браузерах…
очевидно тот, который принят как референсный
а если референсный в этом месте вообще крашится? Или его поведение больше похоже на баг? Или если его поведение не реализуемо для архитектуры, под которую писался не-референсный?
(либо заводи ишью на багу)

this. Спецификация С++ — тоже боженька, в которой (я это гарантирую) — это UB, это UB и это UB. Но по факту выясняется, что интерпретация этого UB у конкретного компилятора или его семейства вполне конкретная. И что хуже — народ на это поведение завязывается. Ну, и как с этим можно работать? :-)


Типичный https://www.hyrumslaw.com/

this
а если мейнтейнеры референсного компилятора не считают это багой, то вы должны внести в свой компилятор то, что считаете багой?
И что хуже — народ на это поведение завязывается
точно так же, как народ завязывается на поведение компилятора раста, которое может поменяться в любой момент любым образом по желанию мейнтейнеров?
Типичный www.hyrumslaw.com
ну вот стандарт и нужен для того, чтобы народ мог завязываться на документированное и гарантированное поведение, нежели на текущее обозримое.
ну вот стандарт и нужен для того, чтобы народ мог завязываться на документированное и гарантированное поведение, нежели на текущее обозримое.

Но ведь он не может. Код надо компилировать сегодня, а не в светлом стандартном будущем.

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

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


Во-вторых, если rustc и mrustc расходятся в поведении, кто из них будет прав? Правильный ответ — никто, ведь нет однозначных требований, которым должна удовлетворять каждая из реализаций.

Ну ничего, обсудят и выпустят какой-нибудь текст (пусть даже в виде ишшуе на гитхабе), разъясняющий поведение. Точно так же, как в случае стандарта C или C++ иногда выпускаются DR'ы, разъясняющие поведение.


Более того, когда clang, gcc и icc ведут себя по-разному, мне как пользователю языка совсем не легче от знания, что кто-то из них, возможно, неправ. Да и совсем не факт, что соответствующая проблема будет в обозримом будущем починена.


Короче, сам факт наличия шильдика ISO ничего не гарантирует и жизнь облегчает не очень сильно. Очень сильно облегчает жизнь минимальность стандарта (шесть уравнений calculus of constructions, помещающихся на одну страницу, я предпочту любому ISO-стандарту), а это явно не случай плюсов.

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

наивно


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

тоже наивно.


А в расте вы нигде не сможете перепроверить ни себя, ни компилятор

точно так же придется топать к разрабам компилятора или писать тест кейсы...

точно так же придется топать к разрабам компилятора или писать тест кейсы...
Да, конкретно у вас в программе поведение является неожиданным независимо от того, кто там прав а кто виноват. Однако не забывайте что вы не наступили на некоторое число багов компилятора только потому, что N лет назад кто-то до вас их отрепортил и их не дропнули с аргументацией «wont fix lol», а пофиксили, потому что «нипастандарту».
список слабых мест C.
Указатели
Управление памятью
Никаких exception, упали значит упали ;)


Извините, как указатели могут быть «слабым» местом? Если управление памятью — это как раз сильное место Си.

Никаких exception, упали значит упали ;)

Это шутка ??? То есть в языке Си нету инструмента для обработки ошибок да?
Упали значит упали, да ??
По моему скромному мнению, самое слабое место в C/C++ — это огромное количество ситуаций с UB, но оно не влияет на начало обучения. Но опять же, если бы мне предложили выбирать подобный язык для начала обучения, я бы выбрал паскаль (и я по-прежнему считаю, что в школе нужно показывать программирование на калькуляторе).
По моему скромному мнению, самое слабое место в C/C++ — это огромное количество ситуаций с UB, но оно не влияет на начало обучения.

Дело даже не в UB напрямую. Вот вы перемножили два intʼа равных миллиарду, а почему вместо произведения вдруг мусор? Нормальное ожидание новичка — или точное значение, или явная ошибка. От UdB он вообще в шок выпадет, но и "тут мы урезали до 32 бит, а что было в реале — сами разбирайтесь" не понравится, мягко говоря.
Python: попытался вызвать метод у None? Вот тебе чётко определённое, понятное исключение. Строка вместо числа? То же самое. Целые числа? Ну вот они тебе до (кажется) 2^1016-1. А если таки вылезешь за границу — привет, опять же понятное исключение.
Это не однозначный аргумент за выбор Python — у него масса других недостатков — но и заметно против C.
Вообще из более-менее массовых языков лучше всего, IMHO, Swift — только надо аккуратно его отвязывать от специфики Apple. Следом — C# и Java. Не-процедурные тут не рассматриваю, это отдельная тема, хотя идея начинать с LISP была не худшей...

Мне кажется, что в перемножение двух интов, приводящее к UB переполнению, не самая большая проблема. Гораздо большей проблемой является то, что даже устоявшиеся программисты зачастую не могут сказать, какой размер у int в сях (кстати, вопрос для самопроверки).

ответ на вопрос для самопроверки
Стандарт предписывает минимальный размер для int в 16 бит (при этом он не обязан быть кратным восьми)


Но опять же, в программах до пятидесяти строк это всё неактуально.
Вообще из более-менее массовых языков лучше всего, IMHO, Swift

Что насчет golang? Статическая типизация плюс минималистичный синтаксис.

Что насчет golang?

Может, у меня перекошенное впечатление, но:
Отказ от наследования — по многим представлениям чуть ли не высочайшая доблесть в ООП, но с современной практикой приводящая к чудовищной плотности бойлерплейта. Писать в таком стиле сильно сложнее, чем с наследованием, и не оправдывает себя.
Невменяемое комьюнити, выросшее во многом на противопоставлении себя другим по принципу "Учение Пайка истинно, потому что верно". Даже споры типа Linux vs. Microsoft не приводили в последние лет 10-15 к такому накалу.
Компиляция статическая, но реально слабая (все средства сборки самопальные и применить туда лучшие мировые практики тупо нет ресурсов).
В сумме, это пока приводило к "не нужно и ещё долго не видно потребности".
"Минималистичный синтаксис" для обучения далеко не всегда полезен.
В общем, может, в десятку и попал бы, но в самом конце.

Зависит от точки зрения.
«Урезали до 32 бит» — я считаю, что это огромный плюс языка С. Новичок хотя бы должен/обязан понять, что существуют биты и байты. А то развелось много программистов, которые даже не знают об их существовании.

Ну вот когда новичок при попытке умножения 1000000000*1000000000 получит ArithmeticOverflowException — это чему-то научит положительному.
А если он получит 2808348672 — при обучении по "бразильской системе", может, 10% и пойдут дальше, но 90% поступят как здесь.
Ваша лично цель какая — обучить, или уменьшить количество возможных конкурентов?

Можно подумать, разница между "ой, моя программа почему то упала" и "ой, моя программа почему то неправильно считает" есть разница.

Можно подумать, разница между "ой, моя программа почему то упала" и "ой, моя программа почему то неправильно считает" есть разница.

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


Вы своим акцентом на "почему-то" пытаетесь, как я понял, намекать не просто на непонимание причин, но и на незаинтересованность ученика в их исправлении. А вот тут и начинается существенное различие. Критически важно при обучении программированию — формирование привычки к тому, что проблемы такого рода будут возникать и их надо исправлять (и что это нудно — даже в самой интересной работе только ~10% того, что отличается от скучной рутины). Но если рабочая среда способствует выявлению ошибок, это лучше, чем если она их тщательно прячет, а для ученика — у которого нет ещё набитых шишек и съеденных собак в поиске предполагаемых проблем — это критически важно.

Упала — это легко обнаруживаемое событие


Не всегда. Для падения по целочисленному переполнению нужно подать на вход алгоритма те самые данные. Unit test поможет, скорее всего. Но он точно так же поможет, или не поможет, в программе на С.

на непонимание причин


Да. Когда всё спрятано глубоко под капотом — причину понять может быть сложно. КМК.
Не всегда. Для падения по целочисленному переполнению нужно подать на вход алгоритма те самые данные.

Да. И в этом случае он явно откажет, а не напишет чушь.


Unit test поможет, скорее всего. Но он точно так же поможет, или не поможет, в программе на С.

Нет. Как только мы выходим в область undefined behavior, начинаются полные непонятки.


Вот одна из знаменитых дискуссий по этому поводу (а вообще их сотни). Заметьте: очень сложно предсказать факторы, которые повлияют, и в какую сторону. Опции компилятора влияют, будут 10 или 112 итераций, а лёгкое изменение кода, которое 1:1 сохраняет семантику, приводит к 3 итерациям. Потом вы взяли чуть обновлённый компилятор, у него что-то иначе по мелочи — сработало первым другое правило, и будет вполне законно 4 итерации или 30, имеет право.


Unit test? Вы протестировали функцию отдельно, но компилятор в реальном коде её заинлайнил и из-за общих оптимизаций после инлайнинга сработали другие оптимизации — и вот ваш unit test превратился в тыкву.


Потому я и говорю: ~95% кода даже на C/C++ все эти высокие оптимизации тупо не нужны и должны быть запрещены по умолчанию. Сложение чисел? Исключение по переполнению. Алиасинг? Считать, что происходит всегда и везде. Пустые указатели? Не выкидывать проверку. И прочая и прочая. И включать разрешения не по какому-то глобальному debug/release, а с управлением по контексту (может, и управляемо от debug/release, но по месту). Вот где программист подумал (и хорошо подумал), там и явно разрешит.
Быстро C к этому не придёт, но "дорога в 1000 ли начинается с одного шага".


Да. Когда всё спрятано глубоко под капотом — причину понять может быть сложно. КМК.

Но когда оттуда прибежит исключение — оно будет видно. А если просто другое значение — и не увидите.
Особенно этим чревата математика. Пусть если X>0, мы идём на ветку "отрабатывать градиентный спуск вниз", а на X<0 — симметрично, вверх. Результат — просто координата вероятно оптимальной точки. Когда вы найдёте, что при определённых достаточно редких условиях реализация грубо лажает?

Есть разница. Потому что, как абсолютно верно заметил netch80, ошибки могут жить годами и быть необнаруживаемыми. Пока не случится катастрофа. Как с боингом или тераком. Лучше уж явное падение, чем неявное ковыляние. Лучше fail fast, чем наоборот. Лучше покрывать код тестами, но тогда, когда разрабатывался Си — это только в проекте было. Сейчас же в том же голанг юнит тесты вообще одно удовольствие писать )

Само по себе понятие exception еще более сложное, чем понятие о битах и байтах. Примерно как сказать, пишите программы как получится, но знайте, что есть «бог», который вас рассудит и наверное протянет руку помощи, если что-то пойдет не так.
Вы еще попробуйте объяснить, как на самом деле работают исключения.
Примерно как сказать, пишите программы как получится, но знайте, что есть «бог», который вас рассудит и наверное протянет руку помощи, если что-то пойдет не так.

наверное, надо скорее учить тому, что бывают непредвиденные ошибки? Ну, не знаю — например, место кончилось, файл нельзя открыть и прочее. И их нужно как-то обрабатывать? И никакого Бога нет )

Я видел вот такие программы написанные новичками:

int real_main()
{

}

int main()
{
try {
real_main();
}
catch( int e)
{}
return 0;
}

Это все, что они могут делать с исключениями.

Ну, есть принципиально два разных подхода — исключения и проверка кодов возврата на все возможные ошибки. Они оба имеют свои недостатки. В принципе, можно комбинировать, но аккуратно. Выглядит так, что второе ок больше для своего кода. Первое лучше для библиотечных вещей, потому что любое исключение своего рода goto и нарушает прямолинейный flow выполнения программы. Но в Python исключения реально структурные и достаточно удобные. А не вот это вот, что написано в С++ style. Кстати, в Си исключений нет и не было (!) Там была история с setjmp/longjmp и какой-то второй механизм, но я забыл.
Ну, и исключения должны быть исключениями, а не штатным поведением… На самом деле дискуссионная тема до сих пор
Например, первое, что выдается с хабра https://habr.com/ru/company/ruvds/blog/533332/

Ладно, не будем спорить. Во многом вы правы.
Все, что я хотел сказать — когда учишь язык C, начинаешь понимать, как на самом деле работает компьютер.
Уча язык Python можно научиться многим правильным технологиям, но трудно понять, как физически работает компьютер.
Все, что я хотел сказать — когда учишь язык C, начинаешь понимать, как на самом деле работает компьютер.

Вообще-то уже давно нет: например, никакой C не позволит понять, например, переименование регистров в Tomasulo algorithm (на котором стоит вся современная out-of-order обработка команд) — это даже ассемблер уже не даёт. Или, например, C ничего не скажет про переключение режимов и почему в x86 через task gate это дорого, а через syscall дёшево.


И вы наверняка не знаете, например, как именно ваш чипсет общается с модулями DRAM: как их память замаплена на адресное пространство, как переставляются адресные линии при выходе на конкретные модули (и почему именно такая перестановка помогает распараллеливанию доступа при типичных шаблонах потребления), что именно делает южный мост (даже если в нёт Management Engine) при старте компа, как выбирается стартовое ядро, и ещё 1000 подобных мелочей… и это разве мешает вам программировать на C, который по сравнению с этим всем примерно так же абстрактен и высокоуровнев, как JavaScript?


Да, какую-то часть C даёт: что есть память с организацией по байтам… да в общем-то и всё. Остальное 1:1 по всей цепочке через Java к Python.


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

Они для 99% и не нужны. Им нужны качественные средства для реализации их задач плюс понимание каких-то азов, типа, почему методы с O(N^3) становятся неэффективны при росте N — ну это одинаково что для ассемблера, что для LISP.

Вообще-то уже давно нет: например, никакой C не позволит понять, например, переименование регистров в Tomasulo algorithm (на котором стоит вся современная out-of-order обработка команд) — это даже ассемблер уже не даёт. Или, например, C ничего не скажет про переключение режимов и почему в x86 через task gate это дорого, а через syscall дёшево.

все хуже. Си вообще не говорит о том как именно будет сделана реализация float и double. Я помню еще времена, когда при прочих равных было выгоднее делать int арифметику, а float был быстрее double. Сейчас вроде уже разницы почти нет, но нюансы на микропроцессорном уровне все еще есть.


Отдельная история — это нюансы с быстродействием. Почему какой-нибудь цикл for (… ;--i) не эквивалентен for (…; i++) или for (…; ++i)

Ну, есть принципиально два разных подхода — исключения и проверка кодов возврата на все возможные ошибки.

Что-то там про монады, что-то там про видимость в типах, что-то там про невозможность скрыто игнорировать.

Это все, что они могут делать с исключениями.

Всё что они могут нормально сделать — это просто не ловить, и программа вылетит. А тут уже кто-то сделал грубый хак. Кстати, скорее не catch(int e), а catch(...).
И вот тут дело учителя — за такой хак бить по виртуальным рукам точно так же, как за любые другие диверсии.

Всё что они могут нормально сделать — это просто не ловить, и программа вылетит.

Эм, нет. Отсутствие обработчика исключения — опасная штука, так как stack unwinding при отсутствии обработчика исключения — implementation-defined, и gcc, например, делает это не всегда.


Поэтому если у вас вдруг на стеке есть объект


struct MissileController
{
    ~MissileController() { abortLaunch(); }
};

void foo()
{
    MissileController c;
    if (something)
        throw Error { "uh oh" };
}

и во всей цепочке вплоть до main нет обработчика исключения Error, то деструктор c может и не вызваться. Неприятненько.

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


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

так как stack unwinding при отсутствии обработчика исключения — implementation-defined, и gcc, например, делает это не всегда.

Если программа вылетает… как рядом сказал коллега mayorovp, для учебной программы это уже неважно. Она написала сообщение об ошибке (с текстом исключения), к ней можно прицепиться отладчиком… вариантов много.
Для продуктина я, конечно, буду всё это ловить (причём особенно не в главных нитках — нефиг всяким тредпулам вышибать программу, что бы в них ни происходило (ну, почти)).

А в значимой части случаев это именно что всё, что вы можете сделать с исключением.


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


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

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

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

Не "рука помощи от бога", а опознание ошибок и репорт их. Вот, например, вы открываете файл по некоторому пути на запись, а он оказался каталогом. Все ОС скажут "иди нафиг". А надо было заранее проверить, а иначе ОС накидает туда кучу мусора, так? Или чтобы писалось в пустоту?


Вы еще попробуйте объяснить, как на самом деле работают исключения.

Для объяснения для того, кто только учится программировать, достаточно, что управление безвозвратно (вот это я в 96-м или около того, когда читал про исключения, понял ой не сразу) передаётся в ближайший адекватный catch (местами except).
А как оно внутри реализовано — это уже ближе к "сеньору".

Не "рука помощи от бога", а опознание ошибок и репорт их. Вот, например, вы открываете файл по некоторому пути на запись, а он оказался каталогом. Все ОС скажут "иди нафиг". А надо было заранее проверить, а иначе ОС накидает туда кучу мусора, так? Или чтобы писалось в пустоту?

Надо, чтобы вы не могли использовать открытый файл при наличии ошибки. Этого добиваются и исключения, и алгебраические типы данных вида Result<File, IOError>, но из этих двух альтернатив только одна показывает, что ошибка может возникнуть именно здесь и заставит обработать её программиста, а не компилятор.


Для объяснения для того, кто только учится программировать, достаточно, что управление безвозвратно (вот это я в 96-м или около того, когда читал про исключения, понял ой не сразу) передаётся в ближайший адекватный catch (местами except).

Только оно передаётся не туда, точнее, не сразу. В C++ есть деструкторы. В Python есть finally и __exit__ (который, кстати, ещё может отменить исключение, так что до «ближайшего адекватного except» дело не дойдёт). Объяснить RAII или использование with придётся, если вы хотите, чтобы программы могли адекватно обрабатывать исключения, а не просто падали с другим сообщением об ошибке.

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

То есть вы за принуждение к обработке ошибок на месте, я правильно понял? Я вообще-то говорил немного о другом. Тот же случай открытия каталога на запись в обычном Unix* libc вернёт -1, и дальше уже обязанность программиста проверить (может, сгенерировать исключение, если хочет), но по крайней мере не пустит сделать диверсию. Как именно ловить и обрабатывать ошибку уже следующий вопрос. Вон в Python есть connect(), который генерирует исключение, а есть connect_ex(), который возвращает числовой код. Жаль, что таких мест в нём мало, и почему-то некоторые решения странные (например, поиск в строке выдаёт -1 по ненахождению, а в списке — исключение).


Только оно передаётся не туда, точнее, не сразу.

То, что вы пишете, уже следующий уровень понимания. Он нужен, но вначале надо понять первый уровень — что исполнение завершается тут на throw (raise) и продолжается на подходящем catch (except).

То есть вы за принуждение к обработке ошибок на месте, я правильно понял?

Пока к принуждению прикладывают синтаксический сахар вроде ? — да. Если не прикладывают, то я считаю, что это всё ещё лучше исключений, но работать уже не удобно.


Я вообще-то говорил немного о другом. Тот же случай открытия каталога на запись в обычном Unix* libc вернёт -1, и дальше уже обязанность программиста проверить (может, сгенерировать исключение, если хочет), но по крайней мере не пустит сделать диверсию.

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


С тем, что ошибки случаются вроде никто не спорил. Ранее спорили с тем, что считать ошибкой конкретно при перемножении целых. А тот ваш комментарий, на который ранее в этой ветке ответил я, был ответом на комментарий с содержанием примерно «при исключениях непонятно, что происходит» без продолжения спора о том, что считать ошибкой при использовании целых. Логично, что ответ вида «ошибки случаются, нужно ли при возникновении ошибки воспринимать её как UB» на комментарий «исключения непонятны» воспринимается как «ошибки случаются[, значит, нужно выбросить исключение, потому что UB это плохо]».


Если вы намекаете на то, что практики из домена взаимодействия с внешним миром нужно применить к домену математических вычислений, то об этом хорошо бы написать явно. Я лично с идеей не согласен: от перехватываемой ошибки тут толку намного меньше, выбрасывание паники с понятным сообщением вполне подойдёт для обучения (и можно даже не упоминать про возможность её перехвата — ни наличие такой возможности, ни проход по стеку с вызовом Drop::drop всё равно не гарантируются).


(А в реальных программах мне кажется лучше иметь, на выбор, возможность использования * как panicing_mul/checked_mul/wrapping_mul/…, или способ доказать компилятору, что здесь переполнения не может произойти — с ошибкой компиляции, если доказательство некорректно. У Rust пока есть только первый вариант в не слишком удобном виде (но, возможно, кто‐то сделал более удобный макрос).)

Пока к принуждению прикладывают синтаксический сахар вроде? — да. Если не прикладывают, то я считаю, что это всё ещё лучше исключений, но работать уже не удобно.

Вот почему "всё ещё лучше исключений", непонятно.
Варианты Option, Nullable, и т.п. — это, да, удобнее и чётче всяких "(size_t)-1 как признак ошибки", но абсолютизировать их тоже нет причины. Лучше всего они как раз на нижних уровнях интерфейсов, где сейчас имеем тупые костыли типа "получил INT_MAX — загляни в errno".


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

Это уже усиление моего тезиса, а не его парирование.


Логично, что ответ вида «ошибки случаются, нужно ли при возникновении ошибки воспринимать её как UB» на комментарий «исключения непонятны» воспринимается как «ошибки случаются[, значит, нужно выбросить исключение, потому что UB это плохо]».

Я уже в вашем ответе перестал понимать — вы согласны с этим (что исключение в общем случае лучше UB) или нет?


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

Что именно вы называете такими практиками?
Моя позиция достаточно проста и прямолинейна:
1) Бо́льшая часть кода требует поддержки компилятора/среды/etc. для защиты от любых ошибок, включая те, которые программисту сложно отловить в силу его специфики человеческого мышления, даже если опознаются заранее. И только малая часть кода должна писаться с расчётом на "мы тут проверили всё и разрешили компилятору максимум свободы".
2) Средства реакции на ошибку должны как можно раньше давать её вскрытие и переключение на обработку.
3) Для большинства современных языков механизмами для (2) являются исключения (в Go, Rust — паники), тем более что они за счёт неявного побочного канала позволяют писать в духе a+b*c вместо даже sum(a, product(b,c).unwrap()).unwrap() (или как там в более новом Rust, не помню).


Что из этого списка у вас конкретно вызывает возражения, почему и какие?


И почему вы называете "доменом математических вычислений" арифметику в языке? Даже если это с какой-то точки зрения формально правильно, для данного вопроса это неоптимально.


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

Вы за то, чтобы паника была, но перехватывать её было нельзя? Тогда — почему?
И как тогда учиться тому, что ошибки надо тоже уметь обрабатывать?
И как учиться тому, что ошибка может быть обязательной частью нормальной работы? Например, множество программ ищут свои конфиги в порядке: ./foo.conf, ~/.config/foo.conf, ~/.foo.conf, /etc/foo.conf, и отсутствие части из них это норма для 100% случаев — что, нельзя такое вообще ловить? Где предел вашей позиции по запрету ловить ошибки?


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

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

Вот почему "всё ещё лучше исключений", непонятно.

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


Я уже в вашем ответе перестал понимать — вы согласны с этим (что исключение в общем случае лучше UB) или нет?

Исключение действительно лучше.



Что из этого списка у вас конкретно вызывает возражения, почему и какие?

Из этого списка возражения не вызывает ничего, кроме того, что у Rust механизм для 3 — это Result, а не паника (если вы, конечно, хотите переключаться на обработку).


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


Вы за то, чтобы паника была, но перехватывать её было нельзя? Тогда — почему?

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


В меньшинстве случаев есть checked_….


И как тогда учиться тому, что ошибки надо тоже уметь обрабатывать?

Учиться обрабатывать ошибки на других функциях? Если хочется обязательно на ошибках в арифметике — checked_….


И как учиться тому, что ошибка может быть обязательной частью нормальной работы? Например, множество программ ищут свои конфиги в порядке: ./foo.conf, ~/.config/foo.conf, ~/.foo.conf, /etc/foo.conf, и отсутствие части из них это норма для 100% случаев — что, нельзя такое вообще ловить? Где предел вашей позиции по запрету ловить ошибки?

Это другой домен. Я не вижу причин, почему ошибки в арифметике должны работать так же, как и ошибки в IO. Причин делать обработку по‐разному же полно:


  • производительность;
  • удобство — при АТД вам будет нужно делать .unwrap() или другую обработку на каждое арифметическое выражение, у исключений тут правда преимущество: вам не нужно будет делать больше телодвижений, учитывая, что вам уже нужно везде применять RAII или аналог просто по факту существования исключений;
  • разные причины их возникновения — файл может пропасть, потому что пользователь решил почистить /etc, отправил его в NFS и отвалился сервер, …, в общем если это не опечатка в названии файла, то это не баг в программе, а переполнение — это обычно либо баг в программе, либо некорректный ввод (что тоже часто баг вида «недостаточно проверили ввод»).
удобство — при АТД вам будет нужно делать .unwrap() или другую обработку на каждое арифметическое выражение, у исключений тут правда преимущество: вам не нужно будет делать больше телодвижений, учитывая, что вам уже нужно везде применять RAII или аналог просто по факту существования исключений;

Зачем? Будет там какое-нибудь (+) :: Num a => a -> a -> Either ArithError a и do-нотация при использовании. Компилятору это никто не мешает разворачивать в настолько же эффективный код, как и сейчас.

Ну-ну. Простейшая формула вида (a+b)*(c-d) в
этой вашей do-нотации в такой кошмар превратится...

Добавятся у вас ещё операторы для того, чтобы записывать такие выражения в строку (условный liftJoin2 :: Monad m => (a -> b -> m c) -> m a -> m b -> m c и (<+>) = liftJoin2 (+) и так далее), ничего страшного.


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

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

а еще компьютер могут обесточить )))

Изучал C, потом C++. Как работают исключения почему-то понял сразу и без «надрыва» мозга. Дело было ещё в школе, если правильно помню. Оказалось, что всё просто, если понимать, как всё работает на уровне стэка, а эту часть я успел разобрать, когда изучал C.

Тем не менее, например, Windows SEH умеет возвращать, если попросят, управление обратно (может исправить условия для корректного выполнения кода), а исключения C++/Java/Python/etc. — нет, только безвозвратная передача. Всякие бейсики с on error resume next тоже дают другие варианты.
Почему легче поняли — не знаю, может, этот момент прямым текстом был описан. Я это учил по Страуструпу, а он много где откровенно так шифруется (о чём явно пишет).

С Windows работал очень мало, а тамошним скриптовым движком вообще не работал. Пробовал разобраться и понял, что не окупится. Я изучал по какой-то книжке, которую, возможно, ещё получится найти. Для понимания механизма мне хватило упоминания развёртки стэка и вызова деструкторов для всех объектов, созданных в стэке, до точки, где ловится исключение. Возможно, я лучше понимал механизм работы стэка. Правда, я начинал изучать программирование довольно странным способом и среди прочего, что я изучал ещё до начала полноценной работы с Си, были ассемблер процессора i8080, а так же продирался через книжку, в которой разбирался компилятор Small C. В общем, шёл от железа.

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

Оно как раз не позволяет программисту абстрагироваться от того как устроен компьютер

Увы, не так.


Если вы читаете доку по команде умножения, например, на x86, там будет сказано, например (IMUL трёхоперандный)


The intermediate product (twice the size of the first source operand) is truncated and stored in the destination operand (a general-purpose register).

"все слова несущие" © — про обрезание результата сказано явно, и кодеру подсказано думать "а может ли быть переполнение? должен ли я его проверять?" и средства даны:


The CF and OF flags are set when the signed integer value of the intermediate product differs from the sign extended operand-size-truncated product, otherwise the CF and OF flags are cleared.

Можно аналогичное поискать для ARM:


result = UInt(operand3) + (UInt(operand1) * UInt(operand2));
X[d] = result<destsize-1:0>;

пусть и не столь явно, но про урезание сказано.


Это всё чётко определённые операции. Тут утечки нет.


А что в C? Пусть даже стандарт:


The result of the binary * operator is the product of the operands.

учебники скажут примерно 1:1 про это же. Каким образом получается произведение, а не результат его усечения или вообще что-то левое? Где средства получить данные, получилось умножение или нет? Их не просто нет — проблема вообще не названа. Да, это и есть дырявая абстракция в полный рост.


В GCC и Clang, слава всем богам, добавили __builtin_mul_overflow(). Одно из того, что я хочу видеть в стандарте — это его же, пусть и с другим названием. (Но это только первый, базовый, уровень, а надо идти дальше.)
Вот с ним уже будет честная абстракция.


Если же вы предлагаете, чтобы обучаемый набил шишек на том, что стандарт тут не улучшился за 50 лет, и был готов к подобному на будущее… не спешите. И без C/C++ будут тысячи таких проблем, но вначале они могут только организовать "выученную беспомощность".

Это проблема Си в плане использования в реальной работе, но зато препод может показать студенту (если уж продолжать пример с перемножением) "вот смотрите, компьютеры так перемножают числа, вы можете сами попробовать и да, стандарт кривой". А на питоне "сами не попробуют", будет скрыто за эксепшном.

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

Да, это и есть дырявая абстракция в полный рост.
архитектуры не ограничиваются x86/arm, и режимы сборки не ограничиваются релизом/дебагом…
архитектуры не ограничиваются x86/arm

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


и режимы сборки не ограничиваются релизом/дебагом…

Я где-то предлагал ограничиться? Мой "поинт" совсем о другом: контекстное управление тем, что позволено компилятору, важнее общего режима сборки (хотя может корректироваться под последнего).

если не генерируется исключение
а если гарантируется? Стандарт с++ ведь должен учитывать даже такие платформы, а не только парочку общего назначения

что такое исключение на уровне ассемблера, простите?

Прерывание, например. Можно погуглить по «hardware exceptions», и там транзитивно походить по ссылкам по мере интереса.


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

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

System/Z: при установленном бите 20, команды знаковой арифметики (те, что не имеют logical в названии) генерируют прерывание при обнаружении целочисленного переполнения.


MIPS: команды знаковой арифметики (например, add, в отличие от беззнаковой addu) генерируют прерывание при обнаружении целочисленного переполнения.


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


Ну и into в x86, очевидно — аналогично.

Специальная инструкция, выбрасывающая в ядерный обработчик исключения. Раньше int 21h использовали (ну и другие) для функций DOS'а, сейчас чаще встречаются ошибки по делению на ноль, по доступу к памяти и т.д.
а если гарантируется?

Имеется в виду "генерируется"? Простите, специфика контекста требует уточнения. Пока буду полагать именно такое прочтение.


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

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


Если смотреть с практической точки зрения, то сейчас компиляторы даже при таких возможностях платформ их стараются не использовать. В System/Z, флаг PSW о генерации исключения по переполнению не ставится в известных мне ABI. В MIPS, есть add и addu, но GCC, Clang генерируют addu даже там, где однозначно операция со знаковыми. И я очень плохо представляю себе платформу, где исключение тут неотключаемо…
X86 с его исключением от деления на 0 здесь скорее исключение (не каламбур).

Ну вот они тебе до (кажется) 2^1016-1.

В cPython длинная арифметика для целых чисел. Только что сделал print(2**1000000), напечатало оооочень длинное число.

огромное количество ситуаций с UB, но оно не влияет на начало обучения

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


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

Но зачем? Программирование уже давно не (только) про вычисление.

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

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

Я преподаю в ВУЗе, и на первый курс у нас приходит множество студентов, которые компьютера дома не имеют, и программирования в глаза не видели. В первом семестре они знакомятся с программированием, причём на питоне. И я вижу изрядное количество проблем (две из которых я привёл), которые случились исключительно из-за неинтуитивности языка.

Но зачем? Программирование уже давно не (только) про вычисление.

Да, но первый цикл всё же проще написать, когда магии вокруг нет, и понятно всё происходящее. Другой вопрос, что с калькулятором надолго не надо оставаться :)
А вообще (один раз) написать какой-нибудь морской бой на калькуляторе — почему нет?
Да, но первый цикл всё же проще написать, когда магии вокруг нет, и понятно всё происходящее

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


А вообще (один раз) написать какой-нибудь морской бой на калькуляторе — почему нет?

Потому что зачем? Какую задачу решает это обучение?

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

Зачем это делать на калькуляторе? Почему это не делать в браузере, чтобы результат можно было кому угодно показать.

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

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

А что это меняет? И на водительские права в очень малом количестве школ сдают, но это же повод сказать, что в школе учат решать квадратные уравнения, поскольку мопедов/автомобилей нет?

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

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


А вообще у калькулятора есть очень большое преимущество минимального количества магии.

А это преимущество? Я вот не уверен.


А для тех, для кого это преимущество, есть Code Перцольда, кстати.

(В самом начале) не надо учить языку программирования. Надо учить основам. Чем интуитивнее инструмент для изучения основ, тем лучше.

А языки придут потом. Всё равно ни один вуз не может выпустить готового специалиста по C++ (ни по питону), поэтому не надо делать ставку на то либо другое сразу же после того, как показали ученику, где находится кнопка вкл-выкл.
Чем интуитивнее инструмент для изучения основ, тем лучше.

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

Подождите, а в браузере что загружено-то?

Обучающий сайт.

а в браузере что загружено-то?

JavaScript же.

Простите, а какой программируемый калькулятор вы имеете в виду?
Советский МК-61? Какой-то из современных, какой именно?

Ой, я не имел в виду какой-то конкретный. В местных школах широко распространены Casio fx-3650P II, для первых опытов вполне пойдёт.

Я считаю, что питон для начинающих — отравленный подарок
На Python чрезвычайно легко начать писать программу. На С чрезвычайно просто начать писать алгоритм. Но если надо написать приложение — следует использовать соответствующие фреймворки и языки.
Ну давайте я агрумент приведу, хоть я об этом и писал совсем недавно в соседней теме. Я считаю, что питон для начинающих — отравленный подарок, поскольку его правила трудоформализуемы для начинающих. В итоге как только размер программы у начинающего превышает пару сотен строк, начинается борьба с неочевидными (для начинающего) глюками.

Вот для того, что бы таких «проблем» не было у новичков — я всем (кто хочет «войти в ИТ») рекомендую нормальные курсы смотреть, например базовый курс на русском по Python для начинающих на 35 (!!!) часов или Python Complete Masterclass for Beginners на 30 часов
. Там все такое разбирается.
Так же можно всегда почитать официальный туториал на английском.

Дом без геологоразведки же не строим? Так почему к Python мы относимся как к чему-то «простому»?
Дом без геологоразведки же не строим? Так почему к Python мы относимся как к чему-то «простому»?

Собственно, я об этом и говорю: именно в том и проблема, что питон очень часто используется не по назначению.
ППКС (также читал ваш комментарий в соседней ветке)
Преподаю в школе: дети увлеченные, глубокие, работоспособные в большинстве.
Но даже их развращает «питономания». Вместо понимания и желания сделать качественный проект процветает «найду библиотеку», «проект за 3 дня». И с таким подходом приходится бороться.

Первым языком идёт Паскаль, так как не все в классе программисты.
Но после мучений с первым языком дальше даём Питон. Причём в стиле «программирование не для программистов» — много библиотек и всяких интересных прикладных задач. При этом базовое использование синтаксиса.

У меня на Паскаль строгая аллергия. Уж не знаю. Язык сам, наверное, неплохой. Но хочется, чтобы учили чему-то практически полезному. И здесь пайтон выигрывает 100 очков вперёд. С другой стороны, действительно нет разницы на чем писать алгоритмы ) надо провести опыт и попробовать жене (которая вообще нуб в программировании) показать голанг )))

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

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

Списки можно распаковывать звёздочкой.

Если кого-то пугает scoping rules, то можно вспомнить, что Питон таки ООП, и не писать голые функции, а прятать всё в объектах.

И это если не стоит задача просто слоёв в керасе накидать для тензорфло.

Если кого-то пугает scoping rules, то можно вспомнить, что Питон таки ООП, и не писать голые функции, а прятать всё в объектах.

А теперь расскажите, как это объяснять тому, чей опыт программирования — пара месяцев.

Никак )

А надо?

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

Бомбануло с примера после 2 10!!! По приколу бывает балуюсь на своём питоне, но так вымораживает бывает. Вроде всё правильно делаешь, но оно не заводится. Чуточку перефразируешь код — работает… Думал у меня галюники и чертовщина какая-то в этом питоне, а оно вон как!) ууу. Так а в чём там проблема в 3 примере? Или для питонистов это не объясняется?)
Ну всё просто (когда знаешь правила видимости переменных в питоне): по умолчанию на чтение переменная глобальная, на запись локальная.

В третьем примере в функции есть запись в переменную i, поэтому она определена как локальная во всём блоке. Но при этом есть попытка распечатать её содержание до объявления, что компилятору не нравится, всё логично.

Но у меня регулярно сносит мозг от того, что есть переменные локальные, есть глобальные, а есть НЕЛОКАЛЬНЫЕ! См. ключевые слова global и nonlocal. Я знаю далеко не все языки программирования, но такую логику видел только в одном месте.

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


И, вообще говоря, я не вижу причин использовать ключевое слово global, можно от него отказаться и использовать только nonlocal. Тогда ничего мозг сносить не будет.

И, вообще говоря, я не вижу причин использовать ключевое слово global, можно от него отказаться и использовать только nonlocal. Тогда ничего мозг сносить не будет.

nonlocal не даёт доступ к глобальным переменным, проверяется банально.


$ cat t1.py 
a = 1
def f():
    nonlocal a
    print(a)
f()
$ python3 t1.py
  File "t1.py", line 3
    nonlocal a
    ^
SyntaxError: no binding for nonlocal 'a' found

Хм, даже так? И правда "хороший" язык, однако...


Впрочем, изменяемые глобальные переменные всё равно не нужны.

Как-то особенно и аргументов не увидел…
Во всяком обучении важна мотивация. На Python очень простые и классные библиотеки для всего, которые осваивает даже ребенок. То же самое касается языка программирования.
Когда можно с минимальными знаниями уже написать игру или еще какую-то программу, которая нравится, то это очень мотивирует к изучению принципов программирования.
А дальше доизучать низкоуровневые вещи, которые видны в C и стиль типизированного программирования — это не сложно.
Сама идея о том, что можно что-то испортить начав не с того — она довольно абсурдна. Можно наверно неправильно научиться держать музыкальный инструмент а потом долго переучиваться, это да. Но истины можно познавать в любом порядке. Здесь нет опасность испортить себе стиль, начав не с того.
Сама идея о том, что можно что-то испортить начав не с того — она довольно абсурдна

почему? На самом деле в этом есть некий смысл. Как у нас устроен процесс обучения в учебных заведениях? Что-то изучаем, а потом "дети, забудьте все, чему вас учили, потому что на самом деле так..." И так несколько раз. Если человек запоминает что-то неправильное или формирует неправильные привычки — потом гораздо сложнее перебить это, чем сразу учить правильно. Та же история с языками программирования. Есть дилемма. То ли изучать это все сверху вниз (от python к Си и ассемблеру). То ли наоборот — от каких-то фундаментальных вещей (регистры процессора, виртуальная память — к более высоким слоям абстракции). В первом случае мы действительно можем сразу получать практические результаты. Во втором — мозг раньше закипит, чем получишь что-то осязаемое, зато сразу понимаешь что и как работает.
С третьей стороны — алгоритмы вообще инвариантны к ЯП. Какая разница на чем писать пузырьковую сортировку?

Если человек запоминает что-то неправильное или формирует неправильные привычки — потом гораздо сложнее перебить это, чем сразу учить правильно.
Вот это вообще не верно, если вы только не задаетесь целью надрессировать обезьяну. На самом деле есть некая скрытая истинная картина, которую в конечном итоге нужно понимать. Эта картина может приоткрываться с любой стороны и в любой последовательности. Лишь бы был интерес к обучению. Если часть картины открыть не совсем верно, это тоже лучше чем вообще ничего — исправить знание всегда проще чем понять с нуля.
Если у человека недостаточно ума, чтобы эту картину понять, то в какой последовательности его не учи, он все равно не будет эффективен. Если достаточно, то все равно в итоге у него будет вся картина.
Если вы изучили нечто, что выглядит как магия, то тем интереснее и проще изучать что находится внутри этой магии и как она работает. Потому что сразу будет понятно зачем существуют те или иные базовые вещи и сразу есть полигон для отработки.
И наоборот, если вы изучили сначала базовые вещи, еще не понимая что из них можно слепить, то дальше проще изучать как из них получается эта магия.
Никакой из этих путей ничего не портит, можно выбирать любой, если он нравится.
ИМХО, все рассуждения о том, чего нельзя изучать в начале — это оправдания лентяев, которые не хотят начать с неправильного, а в итоге не начинают ни с чего.
Или еще бывает другой вариант — в программисты попадает человек с гуманитарным складом ума и он все время позорно не понимает базовых вещей, хотя обучается оперировать с высокоуровневыми. В итоге окружающие специалисты делают вывод, что он был испорчен тем, что начал с высокоуровневых вещей. Но на самом деле, если бы его начали учить с C и ассемблера, он все равно ничего не запомнил а только возненавидел бы все программирование. А уже когда он программирует, то заставить его понять низкоуровневые вещи будет даже легче, потому что теперь он хоть понимает зачем это ему.

У меня был знакомый, который создал компанию, нанял программистов и вывел на рынок продукт. При этом попутно немного вник в веб-программирование и изучил основы одного ЯП. А потом прочитал книгу для детей об основах информатики и был от нее в страшном восторге, типа теперь ему стал понянен смысл всего программирования. При этом то что он прочитал в этой книге он читал с детства на каждом заборе, но это все не имело для него никакого смысла, поскольку он не программист по складу ума и его мозг не видел зачем это все, а когда он уже видел зачем, то сумел понять и базовые концепции.
Как у нас устроен процесс обучения в учебных заведениях? Что-то изучаем, а потом "дети, забудьте все, чему вас учили, потому что на самом деле так..."

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

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

Вы даже не представляете как сильно правы. Мозг это тоже своеобразный компьютер, который имеет свои языки программирования. Буквально вчера читал о подобных ошибках http://www.roman.by/r-78352.html

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

Как раз наоборот, идея очень здравая. У каждого языка программирования свои трудности, свой порог вхождения и кривая обучения. Скажем, пока студенты, изучающие С, набивают шишки на указателях и управлении памятью, студенты-питонисты уже вовсю пишут вполне сложные программы с практической ценностью. В результате многие выпускники-сишники вспоминают о программировании с ужасом и отвращением, а вот питонисты вполне себе уверены в себе (мое личное впечатление, не является аксиомой).

Ну и замечено, что первый язык программирования (точнее, первый «зашедший» человеку язык программирования) накладывает заметный отпечаток на стиль мышления и работы программиста в дальнейшем.
В результате многие выпускники-сишники вспоминают о программировании с ужасом и отвращением, а вот питонисты вполне себе уверены в себе (мое личное впечатление, не является аксиомой).
Но это же не значит, что мы испортили программистов обучив их C. Просто обучение было не эффективным. Нет проблем теперь начинать учить сишников питону (или проще тогда C#).
Ну и замечено, что первый язык программирования (точнее, первый «зашедший» человеку язык программирования) накладывает заметный отпечаток на стиль мышления и работы программиста в дальнейшем.
Стиль мышления скорее зависит от решаемых задач чем от языка. И от них же зависит выбранный язык.
Но это же не значит, что мы испортили программистов обучив их C. Просто обучение было не эффективным.

Ммм? Неэффективное обучение == недостаточно уверенные знания и отсутствие уверенности в своем коде == никакой программист.

Стиль мышления скорее зависит от решаемых задач чем от языка. И от них же зависит выбранный язык.

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

Что касается выбора первого ЯП для будущего профессионального программиста, то это вообще вопрос неважный. ЯП — вообще не проблема и изучаются очень легко, если это не Хаскель. Сложности начинаются когда надо написать десятки тысяч строк кода и не утонуть.
Вообще-то нет. Мы говорим о том, какому языку учиться, если учишься сам и у тебя есть выбор: такому, который больше нравится и больше соответствует задачам, которые нравится решать.

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

А вот если взять сферического в вакууме неофита, у которого глаза горят заняться программированием — за какой язык ему схватиться, если он ни одного из них в глаза не видел? Из чего ему исходить, если он еще не знает, какие ему задачи нравится решать — ведь он еще не решал задачи программно? Спросишь такого — так он и игры писать хочет, и блокчейны, и сайты неземной красоты…

Что касается выбора первого ЯП для будущего профессионального программиста, то это вообще вопрос неважный. ЯП — вообще не проблема и изучаются очень легко, если это не Хаскель.

Ну как сказать… Есть чудесные языки (например, С++), в которых подводных граблей больше, чем блох на Бобике. А если их еще и изучать задом наперед, как это у нас принято (от С к С с классами), то изучение первого ЯП может закончиться той самой картинкой «буду проституткой».
Вы это сейчас говорите исходя из своего нынешнего опыта и ставите вопрос, фактически, повышения квалификации.

У меня другой опыт, в другое время. Сначала не программировал, потому что компьютеры еще вообще не продавались не теоретически прекрасно представлял как это делается по статьям в журнале «Юнный техник», потом программировал на бейсике, потому что ничего другого на касете не было, потом программировал на ассемблере, потому что удалось найти транслятор и потому что он позволял сделать диггера или пакмана, который работал быстро. Потом в институте — на фортране, потому что задачи на кафедре были только на фортране. Потом дельфи, потому что он был лучшим и так далее. Бейсик и фортран были изначально созданы для спагетти-кода, а ассемблер в совокупности к кассетным магнитофоном — это вообще ад кромешный. Но это же не значит, что я до сих пор пишу спагетти код или возненавидел программировать. Когда появился CBuilder, я практически рыдал от счастья, созерцая совершенство, мощь и простоту C++ (а вы говорите много граблей).
Сейчас время другое, но принцип тот же. Сейчас школьник открывает питон и сразу может написать кучу очень занимательных приложений для себя, если ему интересно. Или начитает писать моды на java для майнкрафта. Все это прекрасные пути развития навыков IT.
Что касается людей, которые реально начинают изучать свой первый язык программирования в институте — не знаю. Я пока не встречал ниодного такого, кто в последствие стал бы сколько-нибудь ценным специалистом. Однажды собеседовал автослесаря, который пошел программисты (успешно) в 45 лет. Но даже он признался, что впервые научился программировать подростком, купив компьютер «Вектор». Как можно дожить до взрослой жизни в мире, заполненным компьютерами, не попытаться программировать, не понимать ничего в ЯП, а потом вдруг начать учиться я с трудом представляю.
У меня другой опыт, в другое время. Сначала не программировал, потому что компьютеры еще вообще не продавались не теоретически прекрасно представлял как это делается по статьям в журнале «Юный техник», потом программировал на бейсике, потому что ничего другого на кассете не было, потом программировал на ассемблере, потому что удалось найти транслятор и потому что он позволял сделать диггера или пакмана, который работал быстро.

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

Я вот тоже начинал очень похоже. Сначала программируемый калькулятор и программы из журнала «Наука и жизнь» (даже свою программу им отсылал — но было уже поздно, они как раз прекратили публиковать программы на ПМК и перешли к компьютерным). Потом заимел один из вариантов ПК8000 (аж целых 64 килобайта памяти!) — сам разобрался с тамошним бейсиком (писан по мотивам MSX Basic), потом на том бейсике написал свой ассемблер, используя таблицу мнемоник из бумажной инструкции… Методом научного тыка разобрался с перестановкой байт в операндах и со значением многих команд. Тут помогли статьи в «Юном технике», кстати. Потом переписал свой ассемблер на ассемблере (помнится, даже простенький строковый редактор к нему прикрутил). Жаль, что все это уже, скорее всего, не восстановить — я писал на бобинный магнитофон, который уже тогда дышал на ладан…

Да. так я к чему? Я к тому, что если исходить из моего жизненного опыта, достаточно подбросить школьнику какой-нибудь ардуино или распберри — и он самозабвенно начнет на нем писать программы сначала на питоне, потом на си, потом на ассемблере.

На практике же детям это все давно не интересно. А студентам, которые по идее должны уметь программировать, лучше все-таки давать язык, в котором не придется 90% времени бороться с «особенностями».
Потом заимел один из вариантов ПК8000 (аж целых 64 килобайта памяти!) — сам разобрался с тамошним бейсиком (писан по мотивам MSX Basic), потом на том бейсике написал свой ассемблер, используя таблицу мнемоник из бумажной инструкции…
Ага, давайте меряться у кого детство было ужаснее. У меня был Радио-86РК с 24 кБ памяти. Звук издавался установкой и снятием запрета прерываний, дисплей был только алфавитно-цифровым без графики. Но зато ассемблер самому писать не пришлось — отец нашел через знакомых готовый.
Почему ужаснее? Зачем чем-то мериться? Я вообще о другом.

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

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

Так первый зашедший скорее отражает стиль мышления, а не накладывает отпечаток.

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

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

Например, Жак Арсак в своей книге «Программирование игр и головоломок» сокрушался, что ученики, изучавшие Бейсик, склонны потом на любом языке писать путаные программы безо всякой структуры и стиля. Просто потому, что Бейсик не требует никакой структуры, а «машина и так меня понимает».
Книжка, кстати, сама по себе интересная, даром что писана в 1980-х — посмотрел на обложку и вышибло ностальгическую слезу. :)

Бейсик — это, конечно, пример крайний. Но вот я, скажем, на работе ревьюил довольно большой объем кода, и уверенно могу определить, откуда пришел программист в С++. У бывших сишников свой очень узнаваемый стиль, у бывших джавистов — свои заморочки… А ежели человек, не приведи аллах, с функциональных языков начинал — о-о-о…

Бейсик, кстати, Бейсику рознь. Он, вероятно писал, про классический Бейсик с логикой на GOTO и GOSUB. Но уже в QBasic были функции, и учеников можно было учить писать более-менее приличный структурированный код. (Не призываю начинать с QBasic, если что.)


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

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

Он преподавал во Франции в начале-середине 80-х. Тогда там в ходу были микрокомпьютеры а-ля советский «микроша» с убогим классическим бейсиком. Впрочем, еще у них был какой-то свой язык с ключевыми словами на французском… нашел: LSE (Langage symbolique d'enseignement)/

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

Даже через пять лет безошибочно определяются. Разве что с джавистами чуть посложнее — они за это время уже достаточно наполучали по рукам за new без delete и научились пользоваться умными указателями. :)
Даже через пять лет безошибочно определяются.

Я "начинал" с асма, паскаля и бэйсика (qb). Си не зашёл, хотя приходилось и с ним дело иметь. Плюсы и шарп уже из-за пропавшей актуальности не трогал. Можно сказать, давно уже серьёзно не "пишу" (ну, иногда что-то на php или 1С, да скрипты от cmd до ps и bash)… но моя школьная информатичка всё равно "узнала по почерку" программу, которую я, спустя 15 лет по окончании школы, набросал племяннику бывшего одноклассника. И да, свой код я стараюсь никому не показывать (ну, кроме кода 1С, который у меня не сильно хуже франчей получается)

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

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

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

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

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

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

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

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

В-третьих, а вы уверены, что ученикам от этого будет нанесена непоправимая польза? Мне вот кажется, что многие их них в результате такого обучения будут путать синтаксические конструкции. Да и само обучение не затянется ли?
В-третьих, а вы уверены, что ученикам от этого будет нанесена непоправимая польза? Мне вот кажется, что многие их них в результате такого обучения будут путать синтаксические конструкции. Да и само обучение не затянется ли?

Контрпример — в некоторых школах одновременно учат несколько иностранных языков. Греческий и латынь. Немецкий и французский. В крайнем случае — учат одновременно родной и один иностранный (английский). И ничего. Низкая эффективность обучения иностранного точно не связано с параллельностью изучения. А скорее с квалификацией педагогов и их низкой заинтересованностью в реальном обучении учеников


Во-вторых, преподаватель должен сам понимать, что он преподает и зачем.

Это да. Но настоящие программисты, которые пишут продакшн код, легко пишут на нескольких языках… Тот же 0xd34df00d наверняка знает более двух ЯП

Контрпример — в некоторых школах одновременно учат несколько иностранных языков.

Так-то оно так, да есть нюансы:
— Язык программирования — это все-таки не язык человеческого общения.
— Учат все-таки не одновременно, а на разных уроках. Еще и учителя обычно разные. Это создает четкое разделение в мозгу, и ученики полностью «переключаются» на другой язык.
— учебная программа по каждому из языков составляется так, чтобы дать требуемый минимум учебных часов.

Если же давать сразу несколько языков программирования на одном уроке — они неминуемо перемешаются в незрелых умах. Да и времени на изучение может не хватить: вместо худо-бедно полного изучения минимума по одному ЯП будет скакание по верхам для двух.
Впрочем, это лично мой скептицизм. Может, не все так плохо, как мне кажется. :)
В РФ в школах сейчас с 1го класса +английский, с 5го класса +немецкий. В некоторых школах экспериментально добавляется киатйский.

У нас в вузе небыло каких то прям требований к используемому ЯП, у нас были последовательно Pascal/С/С++/С#/SQL (по семестру), в рамках которых делали те или иные лабы. На других же предметах не связаных с изучением конкретного ЯП, сдавали кому как и на чём удобнее. И на соседних факультетах ± было так же. Разве не везде так?
Но настоящие программисты, которые пишут продакшн код, легко пишут на нескольких языках…

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

П.С.: поглядывая на ютуб, мне попалось, что в МФТИ так же, сначала питон, потом Си подмножество Плюсов. И один курс продолжает другой.
дальше доизучать низкоуровневые вещи, которые видны в C и стиль типизированного программирования — это не сложно.

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


Но истины можно познавать в любом порядке. Здесь нет опасность испортить себе стиль, начав не с того.

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

Мотивация — да!
Но в чём она? Быстрый красивый результат без знаний? Дети такое с руками оторвут! Урааа…
Только есть загвоздка — «дальше доизучать низкоуровневые вещи, которые видны в C и стиль типизированного программирования это ...» никому не нужно! В принципе. За нас написаны уже все библиотеки! Просто их надо найти и поставить в правильном порядке.

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


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

Но надо сначала определиться, является ли «питономан» приемлемым результатом обучения. Для меня и нашей компании учителей — нет. А рынок сейчас говорит «да» любому, кто написав программу в 10 строк пишет о знании языка.
можно с минимальными знаниями уже написать игру или еще какую-то программу
Ну да, написать что-то вроде
using GameEngine;
CreateGameEngine();

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

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

*Особенно, если на русском языке (привет падежам и склонениям по числам и родам)

Что то жирновато. Особенно о системе сборки и менеджмент зависимостей :)
Вообще то стоит сначала решить ехать или шашечки. Если ехать — пройти хотя бы базовый курс булевой алгебры, компьютерной логики, ассемблера. В общем, всего что лежит в основе. А то что начал с Python, что с С, а под капотом магия, любая внештатная ситуация введет в ступор и непонимание.

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

C отображает истинную картину того, чем является программирование.

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

Лучше вообще писать "Си".

Автор прав — чем больше людей учат не то, тем дороже я буду стоить. Учите С, ребят. Не мешайте мне)
Без обид, но вряд-ли «программист на питоне» может много стоить
Есть у меня знакомый, давно пишет на ++, офигенный прогер, золотая голова, но зарплата всего 1500. Правда если уходить, то можно рассчитывать на чек побольше, но лень. Есть и другой — питонист. Вчерашний джуниор, но поднатаскался в модных докерах и нейросетях. Зарплата 2000. Если уходить, то тоже подрастет чек, но тоже пока лень. Это не Москва, провинция.
Докер к программированию отношение вряд-ли имеет, если он его не разрабатывает. Ну а то, что питонист вклинился в хайповую область, где юзают готовые либы и вся подкапотная работа спокойно списывается на магию, так это уже вопрос рынка. С таким же успехом мы можем и девопсов подтянуть, что скриптики на питоне пишут и получают и того больше, только вот они не программисты. На нормальную зарплату от 4-5к по профилю программиста попасть зная только питончик и не понимая как он работает (ты же не видел С который у него под капотом) я не вижу перспектив.
Питонист это оператор, а Сишник — инженер. Задачи разные, разные и способы их решения.
Водитель тоже оператор, но разве кто-нибудь даст фуру дальнобойщику который даже простую поломку починить не может и не в курсе как у него что под капотом работает?

Вообще так оно и есть. Пилоты самолётов, к примеру, их не чинят. И лучше бы и не чинили.

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

А какое фундаментальное ограничение мешает "программисту на одном фреймворке и языке" починить что-то в этом самом фреймворке или в стандартной библиотеке своего языка?

Не понимая языка нельзя починить фреймворк. А со стандартной библиотекой того же питона надо как минимум понимать как это всё работает под капотом, внезапно на том же С.
Да с чего вы взяли?
Вы еще скажите что на то, кто инженер а кто не инженер влияет то, что человек говорит на русском или английском или китайском.

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

Я уверен, что офигенный прогер на плюсах легко сможет писать и на питоне. Наоборот - тоже сможет, но очень не сразу.

Я не смог писать на питоне :(


Впрочем, я не то чтобы офигенный прогер на плюсах.

Возможно, оно вам и не нужно, решать ваши задачи на питоне.

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

Конечно, когда идёт сложная бизнес-логика, с исключениями, ветвлениями и т.д., тут наш внутренний QA становится раком, пока мы все пути не проверим вручную. И это дико долго + дико тяжело по сравнению с Хаскелем/Ocaml.

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

Компиляция не обязательна для статической типизации (хотя вторая и сильно помогает первой) — вспомним классический Бейсик с его A$ A! A%…
Но с современными средствами можно и интерпретировать код, который в финальной версии будет компилироваться, и при этом проверять на ходу на сотни проблем. Было бы желание...

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

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


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

-fdefer-type-errors :]

а иначе я начинаю чувствовать себя очень неудобно.


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

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

То есть, все эти рейтинги «безопасности языков» скорее показывают планку внутреннего QA у сообщества, сформированного вокруг конкретного языка.
-fdefer-type-errors :]


Я, если честно, не уверен, что даже в таком виде формат Notebook будет хорошо сочетаться с Хаскелем. С другой стороны, туда просится функциональщина (Wolfram language) и иммутабельность (чтобы делать undo-redo, но её никто не реализовал, кмк).

То есть, нужен язык вроде Хаскеля, но не совсем. Классы типов, естественно, нужны — это мука писать вычислительный код на Ocaml (сплошные float_of_int и *.)
Я, если честно, не уверен, что даже в таком виде формат Notebook будет хорошо сочетаться с Хаскелем.

Я с ними вообще не работал, чтобы иметь осмысленное мнение, но вот народ что-то пилит.

синтаксису, напоминающему английский язык
А не Smalltalk ли?
Разработчики на C известны тем, что кропотливо создают упорядоченный, чистый код.

Спасибо, повеселило :))

Удивлённо: Вы разве не открываете вечером избранные прошивки микроконтроллеров и не любуетесь этим чудесно написанным кодом, отдахая душой от рабочих ужасов?

Нет, но я когда-то пару раз ради любопытства открыл C-исходники самых простейших юниксовых утилит, и лучше бы я этого не делал :(

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

Качественный код — это, прежде всего, самодисциплина кодера. И языки C, C++, JavaScript, Python, PHP и т.п. не в состоянии привить эту дисциплину.

Хотите учиться — на начальном этапе берите язык, удобный именно для обучения: маленький, ортогональный (не имеющий множества способов сделать одно и тоже), не позволяющий никакого своеволия, не имеющий кучи правил по умолчанию, особых случаев, исключений из правил, максимально жёсткий к допущенным кодером ошибкам и неточностям. В порядке уменьшения предпочтительности: Oberon-07 (другие языки линейки Oberon тоже прекрасно подходят, но они немного больше и сложнее), Modula-2, классический виртовский Pascal.

N.B. Язык C вполне маленький, но по остальным критериям для обучения категорически не подходит.

Если же учится сразу на модном промышленном языке, то, ИМХО, единственный современный вариант — Go: язык, основанный на C, но вычищенный от всех артефактов конца 60-х годов, не позволяющих писать на C чистый и надёжный код. Эти механизмы языка C имели смысл в 70-е годы (позволяя писать быстрый и компактный код ценой полного отсутствия какого-либо контроля со стороны компилятора), но 2021 году они выглядят совершеннейшим анахронизмом.

N.B. Go — практически единственный маленький современный популярный язык с очень сильной статической типизацией. Большинство модных языков — монстры, разбухшие от совершенно ненужных рюшечек, позволяющих написать код на несколько символов короче.

P.S. Раз статья про обучение кодингу, то и комментарий об этом. Если же говорить не про кодинг, а про программирование, то начинать надо не с языка, а с качественно учебника программирования — который будет учить не приёмам написания кода в конкретном языке, а базовым свойствам алгоритмов (начиная с O алгоритма и того, как именно это О связано с реальным быстродействием) и умению выбирать для данной задачи оптимальный алгоритм её решения.
начинать надо не с языка, а с качественно учебника программирования

У вас есть примеры таких учебников? Которые, в отличие от книг Кнута, сможет прочитать и понять средний человек.
Вирт, «Алгоритмы и структуры данных» (есть 3 с половиной редакции учебника, лучше последнюю).
Кормен, «Алгоримы. Вводный курс».
Хотите учиться — на начальном этапе берите язык, удобный именно для обучения: маленький, ортогональный (не имеющий множества способов сделать одно и тоже), не позволяющий никакого своеволия, не имеющий кучи правил по умолчанию, особых случаев, исключений из правил, максимально жёсткий к допущенным кодером ошибкам и неточностям. В порядке уменьшения предпочтительности: Oberon-07

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

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

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

Но совершенно необязательно изучать именно ML-языки: классический учебник «Структура и интерпретация компьютерных программ» использует Scheme.
Большая часть современного кода пишется на императивных и декларативных (SQL) языках.

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


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

А зачем там обязательно именно взаимодействие? Либо пишете последовательность функций вроде foo $ bar $ baz x (кстати, как в школьной математике — синус от косинуса икса пишется как sin (cos x)), либо, если уж очень надо, вполне себе императивно выглядящее


do
  foo1 <- step1
  step2
  step3 foo1

Но почему именно ML? Классический учебник «Структура и интерпретация компьютерных программ» использует Scheme.

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

В Ocaml код выглядит как императивный:

let () =
   let x = 8 in
   let x = x * x + 2 in
   let y = x + 9 in
   let y = 2*y + x in
   Printf.printf "Hello ";
   Printf.printf "world!\n";
   Printf.printf "I got %d\n" y
N.B. Язык C вполне маленький,

Я бы сказал, что размер C зависит в основном от размера фантазии разработчика. Макрофункции позволяют в общем-то расширить язык таким количеством говнокода в исключительно благих целях, что мало не покажется.
eandr_67
позволяя писать быстрый и компактный код ценой полного отсутствия какого-либо контроля со стороны компилятора

Иногда таки надо показать машине, кто тут хозяин, и использовать магию манипуляций с памятью за пределами стандарта. Не каждый же день что-то в серьёзный продакшн пишешь, который должен работать помимо твоей машины. И как раз именно Си позволяет быть владельцем железа, не снисходя до уровня ассемблерных инструкций в памяти ядра.
Я ведь двумя абзацами раньше специально написал: «на начальном этапе». И всё, что я говорю, относится к C, как языку начального обучения. Язык C — прекрасный инструмент для профессионала, прекрасно понимающего границы применимости «магии», но пулемёт для стрельбы по собственным ногам для новичка. Это как человека, не умеющего водить, посадить за руль болида Формулы-1.

Проблема «магии» C в том, что она используется повсеместно — даже там, где низкоуровневая работа с памятью и близко не требуется. Хотя, по факту, эта «магия» реально нужна — относительно всего объёма современного софта — крайне редко: подавляющее большинство современного кода — прикладной софт, не требующий манипулирования с памятью и железом на низком уровне.

Современные языки тоже умеют на низком уровне, но только явно используя unsafe или аналогичные механизмы. Программист прямым текстом указывает в коде, что здесь и сейчас будет использовать неконтролируемые компилятором низкоуровневые механизмы. По сравнению с С такой подход на порядки уменьшает и локализует ненадёжный код.
Да, вот этот unsafe — это чудесная штука! Но, к сожалению, C — это lingua franca, Rust уже слишком сложен, а остальные замены С малоизвестны.
А за счёт чего сейчас в коде не требуется низкоуровневая работа? Не потому ли, что как раз вот это всё взаимодействие с железом на Си написали? На формулах-1 кстати любой человек научится водить крайне быстро. При учете, что запас этих машин формулы-1 у нас бесконечный, а время ограничено.
Иногда таки надо показать машине, кто тут хозяин, и использовать магию манипуляций с памятью за пределами стандарта.

Зачем? Ну, кроме развлечений.

А пробегала замечательная статья «What every compiler writer should know about programmers». Если программа рассчитана на конкретную систему с конкретным компилятором, то значительная часть UB становится вполне defined.

Например, на AIX/POWER обращение по адресу NULL даёт страницу, заполненную нулями. Соответственно, вы можете написать совершенно легальный код

struct Node {
    Node * left;
    Node * right;
};

Node * find_10_left( Node * start) {
    Node * 10_left = start;
    for( int i = 0; i < 10; i++) {
       10_left = 10_left -> left;
    }

    return 10_left;
}


При определённых условиях это может привести к более простому и быстрому коду. Но, конечно, код привязан к этой системе, что, впрочем, случается.
Если программа рассчитана на конкретную систему с конкретным компилятором, то значительная часть UB становится вполне defined.

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


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

Да, в одной компании, где я работал, проект по переносу софта с big iron на линуксы занял несколько лет, если не десятилетие.

Я всё ещё не понимаю, зачем такие программы писать.


Сейчас, в нашей области, это категорически не нужно. Но оно само так получается.

Вы путаете нулевой адрес и нулевой указатель. А ещё вы путаете unspecified behavior и undefined behavior. Второй ни при каких условиях не становится defined.


К примеру, если вы делаете вот так:


Node *left = node->left;
if (!node) {
    printf("Node is null!\n");
}

То компилятор имеет полное право выкинуть весь условный оператор при оптимизации. Даже на AIX/POWER.

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

Это вообще отдельная история и очень частный случай, который надо обсуждать отдельно.


Когда миллисекундная задержка в работе системы скорее всего скажет «всё, весь станок списываем в утиль».

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


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


С тех пор рассказы про станки и миллисекунды на меня производят чуть меньшее впечатление.


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

Ложная дихотомия. Безопасность не обязана гарантироваться рантайм-проверками.

С тех пор рассказы про станки и миллисекунды на меня производят чуть меньшее впечатление.

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

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

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

Которое тоже вполне себе конвертируется в деньги.


Если б вы там на тыщу-другую долларов торговали — да, это ерунда. Но там циферки чуть больше :)


причём в некоторых случаях появляется и риск жизни тестировщика.

Ну это да, здесь есть разница.


Хотя для людей ощутимые материальные последствия тоже есть — например, если очень не повезёт (и если перед SEC не получится доказать, что это просто хак умный, но ты накосячил, а не специально пытался сделать что-то нехорошее), то можно и присесть.


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

А если я статически докажу, что туда уходят только те команды, которые нужны?

Которое тоже вполне себе конвертируется в деньги.

Если б вы там на тыщу-другую долларов торговали — да, это ерунда. Но там циферки чуть больше :)


Если оборудование не серийное, то не конвертируется, как и люди которые умеют с ним работать. А деньги всегда одинаковы, всего лишь абстракции для удобства оценки, это не реальные объекты, которые сложно заменить новыми.

если перед SEC не получится доказать, что это просто хак умный

Если дело доходит до общения с SEC значит вы таки сделали что-то крутое и хорошо работающее, иначе бы они не заинтересовались. «Я просто тестировал фондовые рынки на устойчивость к каскадному обрушению»))

А если я статически докажу, что туда уходят только те команды, которые нужны?

А это не понял: кому, зачем? Если железка выполнила задачу — значит команды были правильные (даже если там были ошибки, которые не разрушили её и не прервали выполнение задачи), если нет — не так уж важно, команды были не те, код баганул, или просто ЭМИ наводка попала не на тот провод.
А это не понял: кому,

Самому строгому проверяльщику в мире.


зачем?

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


или просто ЭМИ наводка попала не на тот провод.

К слову, как против этого обкладываться тестами, я не очень представляю. А вот доказывать, что при флипе до N% битов ваша система выдаёт корректные данные тоже можно.

Открою небольшую тайну: Все люди разные.
У всех разные предпочтения, и способы достижения результата.

Как убить желание изучать программирование в корне?
Начать с языков, на которых в принципе очень сложно что то сделать новичку.
Можно 2 года убить на изучение си, булевой алгебры, регистров процессора, типизации и алгоритмов сортировки, и не написать НИ ОДНОЙ своей программы. (вывести строку и отсортировать список за таковые не считаем, прикладная польза от такого стремиться к нулю)

Или написать на питончике СВОЕГО бота для телеги за 1 месяц.
Разница и подход очевидна. Возможно, найдутся те, кому ппц как зайдёт изучать алгоритм быстрой сортировки, и обмазываться изобретением велосипедов на си.
Но далеко не всем.
На Си пишешь хелло ворд, сортируешь битмап, подключаешь OpenGL выводишь 2D графику, считаешь фракталы, визуализируешь фотографию в 3д спектре, а там и до фотограмметрии с орбитальных спутников недалеко (если датасеты добудешь, благо сейчас с ними уже несложно) и это всё за пару недель реально с нуля по манулам освоить при нормально подготовленном курсе. Потом уже появится понимание происходящего и освоится базовый функционал языка. А на питоне получится всё тоже самое, только потом понимания происходящего уже не появится — механика шестерёнок там закрыта, если прям специально не ковырять.
и это всё за пару недель реально с нуля по манулам освоить при нормально подготовленном курсе.

Хочу увидеть пример такого курса. А то пока что мысли только в сторону такого.

Виртуалка с настроенной IDE, пачкой исходников, готовыми makefile, краткой инструкцией на страницу со всеми командами консоли, чтоб человек в два клика мог всё рабочее запустить, а потом уже разбираться, почему оно так работает по полному многостраничному мануалу, где все строчки и команды в деталях описаны.
Python — язык программирования по умолчанию для «самой сексуальной профессии 21-го века». Да, громкие слова о данных по-прежнему сохраняют за data science репутацию «сексуальной работы», хотя современные обстоятельства уже не полностью поддерживают это утверждение.

Какой тонкий эвфемизм для обозначения куколдинга и бетабаксинга. Особенно для разведенок с 3 детьми такие разработчики «самые сексуальные».

Для обучения программированию лучше всего подходит Go, Си слишком специфичен.

А почему именно C, а не C#, Java или Rust, например? Почему не cpp?

Думаю, что поэтому:

За свою жизнь я поработал более чем с пятью языками программирования, первым был C

Поддержу C# и Java, но не поддержу Rust — он еще более сложен с точки зрения и знаний про устройство программирования и сложности организации абстракций.
cpp ощутимых преимуществ не имеет.
Речь про начало обучения — когда нужно понять прежде всего структурное программирование и базовые элементы — flow и структуры данных.

Rust видится крутым после c и cpp
C: близко к железу, но чтобы научиться не стрелять себе в ногу потребуются годы
Rust: сразу покажет, где, что и как, после него даже на C (какое-то время) будешь писать, аки святой Кармак.

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

А по теме статьи — я, всё-таки, за питон, так как он, что называется, «подсаживает». А именно: создаёт в мозгу зависимость код -> успешный результат -> дофамин. И это главная задача языка для обучения. На других языках это будет пытка -> код -> фрустрация -> ещё фрустрация -> отказ от этого занятия в пользу чего полегче.
Мы же не даём детям сразу раствор и арматуру для постройки игрушечных домиков, нет, сперва даём деревянные кубики или лего, так им проще втянуться.
Потому что чистый Си ближе всего к машинной душе.

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

Это для занятий по микропроцессорной технике.

Там лучше в кодах писать, самостоятельно адресуя память

Адресация памяти в кодах и адресация памяти в мнемониках отличается лишь наглядностью.

Кроме того, довольно многие пакеты Python, допустим, NumPy, написаны на C и его младшем брате C++ для устранения недостатков эффективности, потому что по сравнению с ними Python ужасно медленный.

«Младшем брате» :)