О чем это
Некоторое время назад меня попросили обосновать, почему я за Pascal при обучении программирования. А я скорее не конкретно за Pascal, я за наличие у языка для обучения программированию ряда важных свойств. Получается, я скорее против Python (шутка). В результате получилась эта мини-статья.
Важные для обучения свойства языка программирования:
Перечислим сначала собственно свойства, на которые стоит обращать внимание при выборе языка для обучения программированию.
1. Должен быть явный компилятор
Сразу видно, когда происходит трансляция (что для интерпретатора часто скрыто), это важно для понимания работы инструментов программиста.
Можно показать различие ошибок на этапе компиляции и ошибок времени исполнения.
Сообщение об ошибках компилятора часто понятнее и конкретнее (чем runtime), и как минимум затрагивают меньше возможных причин. А ответ "где у меня ошибка" особенно важен при обучении.
Слабое соображение, но всё-таки, скомпилированная программа, как правило, заметно быстрее выполняется. Это и само по себе интересно показать, и бывает важно на олимпиадах, экзаменах и т.п.
2. Должно быть явное объявление переменных и статическая типизация
Тип данных — важная концепция, и продемонстрировать её гораздо проще, когда тип явно указывается для переменной.
Раздел объявления переменных позволяет проиллюстрировать рассказ о сегменте данных программы и механизмах управления памятью.
Добавляет контроля на этапе компиляции, позволяя раньше и точнее обнаружить ошибку.
Повышает дисциплину ума при программировании, формирует хороший стиль.
Следует избегать языков с развитым автоматическим преобразованием типа данных, чем автопреобразование меньше, тем лучше. Автопреобразование усложняет понимание программы, на некоторых языках есть даже жанр основанных на этом загадок ("WAT"). Также автопреобразование смазывает само понятие типа данных.
3. В синтаксисе языка явное и заметное предпочтительнее скрытого
Значимость непечатных символов должна быть минимальна. Этот принцип можно сформулировать так: на месте любого количества любых невидимых символов можно поставить один пробел, и смысл не изменится. Пробельные символы – не отображаются, на глаз труднее отличить, сколько их идет подряд. Интуитивно трудно принять, что количество пробелов имеет значение. И всё это приводит к неприятным и трудно обнаружимым ошибкам.
Слова заметнее специальных символов, и смысл слова чаще всего понятнее, чем символа (если есть знание соответствующего английского слова). Использование слов ускоряет обучение и снижает количество ошибок (хотя и удлиняет программу).
Обязательное явное указание чего-то часто лучше, чем умолчание. Тут лучше пояснить на примерах, которые будут дальше.
4. Если в языке прямо реализованы какие-то методические идеи, это хорошо
Если язык программирования разрабатывался именно для обучения (или эта цель хотя бы принималась во внимание), автор мог сделать что-то в языке специально, чтобы научить чему-то конкретному. Далее рассмотрим конкретные примеры такого.
5. Есть ещё много соображений для выбора языка
Важна популярность языка, как в индустрии, так и в образовании.
Хорошо, когда для языка есть развитая инструментальная база.
Для разнообразия творческих задач может быть важно, что на этом языке есть много готовых библиотек для решения разных задач в различных областях.
Для обучения, особенно первого изучаемого языка, важен "порог входа" – сколько всего нужно узнать, прежде чем сможешь написать свою первую "настоящую" программу (нетривиальную, не "Hello world"). Имеется в виду "вход в программирование" – минимальная подготовка для самостоятельного движения дальше.
Как видите, важных свойств много, оценки по ним вполне могут противоречить друг другу. И однозначного приоритета у них нет, итоговый выбор – скорее вопрос вкуса и опыта.
Примеры реальных языков программирования
Рассмотрим какие-то из описанных свойств на конкретных примерах языков.
Pascal vs Python
Наконец-то заявленное в названии сравнение. Кроме того, что это просто конкретные примеры, и примеры, реально предлагаемые в образовании, они ещё и "полюсы" диаметрально противоположенных подходов. Поэтому именно их сравнение будет особо показательно.
Трансляция
Pascal – компилируемый. Компилятор выдаёт понятные сообщения об ошибках, удобно встроен в среды разработки. Компиляция происходит в машинный код, программа выполняется максимально быстро и с минимальным расходом памяти.
Python – интерпретатор. Хотя у Python есть "фаза анализа", всё сказанное про смазанность этапа трансляции и диагностику ошибок только во время выполнения к нему вполне относится. Отчасти эти неприятности могут быть скомпенсированы хорошей средой разработки со статическим анализатором кода (типа PyCharm). Работает программа на Python обычно сравнительно медленнее и прожорливее по памяти, чем на других языках (трудно найти язык ещё медленнее). Для повышения быстродействия есть варианты (CPython, PyPy, Jython), но чаще решение находится в использовании внешних библиотек, написанных на C++.
Объявление и типы переменных
Pascal – требует объявления переменных и параметров, проверяет типы на этапе компиляции, чётко разделяет области видимости. Автоматическое преобразование типов ограничено числовыми типами, хорошо описано и мотивировано.
Python – отдельных объявлений переменных нет, они создаются динамически, при первом использовании (например, если ошиблись в имени переменной, будет просто создана новая). Есть глобальная область видимости. Тип переменной не фиксирован, есть по сути только тип значения. Есть объявление параметров функций, есть аннотации для указания типа, но в комплексе это мало меняет ситуацию. Преобразования типов часто явные (функции int(), str()), также есть автоматическое преобразование числовых типов, но их правила сложнее и запутаннее, чем в Pascal, особенно при участии операторов сравнения. Также от числовых (и некоторых структурных) типов в Python не отделён логический.
Покажем пару примеров для иллюстрации. Сначала Python:
x: int = 1
b: bool = True
# Типы тут - только аннотации и не обязательны
print(b + x)
# Выводит "2", никаких сообщений
b = 1
# Выполняется, в PyCharm предупреждение: Expected type 'bool', got 'int' instead
print(b)
# Выводит "1"
b = b + 2
# Выполняется, никаких ошибок или предупреждений
print(False ** False == True)
# Выводит "True", WAT?
x = (1 << 53) + 1
print(x + 1.0 < x)
# Выводит "True", WAT?
И это я ещё не стал приводить примеры с использованием структурных типов данных.
Теперь посмотрим, как те же примеры (или похожие, если точной аналогии нет) выглядят в Pascal:
var
x: Integer = 1;
b: Boolean = True;
begin
WriteLn(b + x);
{Ошибка компиляции: Операция '+' не применима к типам boolean и integer}
b := 1;
{Ошибка компиляции: Нельзя преобразовать тип integer к boolean}
b := b + 2;
{Ошибка компиляции: Операция '+' не применима к типам boolean и integer}
WriteLn(exp(ord(False)) = ord(True));
{Выводит "True",
не совсем то, что в Python (тут нет целочисленного возведения в степень),
но в целом можно понять, что происходит}
var x: BigInteger;
{Ошибка компиляции: Внутриблочные переменные не могут иметь те же имена,
что и переменные из блока верхнего уровня}
var y: BigInteger = BigInteger(exp(ln(2)*54)) + 1;
WriteLn(BigInteger(Real(y) + 1.0) < y);
{Выводит "True",
здесь мы обязаны явно использовать тип для длинной арифметики,
и явное преобразование, чтобы сложить с вещественным числом.
Можно выводить результаты выражений по очереди, и понять,
что дело в ограничении точности вещественных чисел}
end.
Как видно, большую часть странных и запутывающих вещей, что мы сделали в Python, в Pascal нам не позволил сделать компилятор, при этом выдал понятные сообщения об ошибках, и даже на русском языке. Что-то не получилось из-за отсутствия аналогичных возможностей (для учебного языка ограниченные возможности могут быть плюсом). В процессе попыток повторить вычисления, загадки стали яснее из-за вынужденного явного использование функций преобразования. Но если бы не было примера на Python, мы скорее всего и не забрели бы в такие дебри.
Явное или скрытое
Тут в первую очередь приходят на ум значимые отступы на Python. Также можно привести в пример явное обозначение первой ветки условного оператора на Pascal. Рассмотрим код сортировки пузырьком на Python (честно украденный где-то в Интернете):
from random import randint
def bubble(array):
n = len(a)
i = 0
while i < n-1:
j = 0
while j < n-1-i:
if a[j] > a[j+1]:
a[j], a[j+1] = a[j+1], a[j]
j += 1
i += 1
a = [randint(0, 99) for n in range(10)]
bubble(a)
print(a)
И аккуратно переведём его на Pascal (для наглядности максимально сохраняя соответствие строк):
procedure bubble(a: array of Integer);
begin
var n := Length(a);
var i := 0;
while i < n-1 do
begin
var j := 0;
while j < n-1-i do
begin
if a[j] > a[j+1] then
begin
var tmp := a[j];
a[j] := a[j+1];
a[j+1] := tmp;
end;
j := j + 1;
end;
i := i + 1;
end;
end;
const N = 10;
var a: array of Integer;
begin
a := new integer[N];
randomize;
for var i := 0 to N-1 do
begin
a[i] := random(100);
end;
bubble(a);
for var i := 0 to N-1 do
begin
write (a[i]:3);
end;
end.
В примерах специально сделаны одинаковые (и маленькие, всего два пробела) отступы, чтобы подчеркнуть эффект от явных операторных скобок. Большие отступы и подсветка в IDE конечно улучшают ситуацию. В целом на примерах видны и преимущества и недостатки обоих языков. Явность и четкость Pascal оборачивается многословностью. А краткость и «сахарность» Python (вы только посмотрите на кортежное присваивание, или инициализацию списка выражением) — незаметностью ошибок. Даже в приведённом примере на Python есть грубая ошибка (если не заметили, вместо формального параметра процедура сортировки обращается к глобальной переменной массива, и возможность такого — большая беда Python). Pascal, правда, подтягивается по части "сахара". Например, в современных версиях можно объявлять переменные внутри блока (для счётчика цикла это очень уместно), и не указывать тип в случае, когда компилятор может его однозначно определить сам (переменная в результате всё рано имеет строгий тип).
Для подготовки примеров кода использованы:
IntelliJ IDEA 2024.1.2 (Community Edition) + Python plug-in Community Edition + Python 3.12 SDK
PascalABC.NET 3.11, язык Русский.
Пара слов про методические идеи
Часто о Pascal говорят, что он специально придуман для обучения. Это действительно так, Никлаус Вирт, будучи профессором, одной из целей имел обучение студентов структурному программированию. Но хочется показать хотя бы один пример конкретной языковой конструкции, в которой воплотился хоть какой-то конкретный методический приём. И такой пример у меня есть:
type
TFigure = (RECT, CIRCLE);
TFigureParams = record
case figure: TFigure of
RECT: (a, b: Real);
CIRCLE: (r: Real);
end;
function area(f: TFigureParams): Real;
begin
case f.figure of
RECT: area := f.a * f.b;
CIRCLE: area := Pi * f.r * f.r;
end;
end;
var f: TFigureParams;
begin
f.figure := CIRCLE;
f.r := 10;
writeLn(area(f));
f.figure := RECT;
f.a := 2;
f.b := 3;
writeLn(area(f));
end.
Это пример использования записей с вариантами (помните таких?). Видите, как похоже описание типа данных и код его обработки? Это и есть методическая идея Вирта: аналогия между алгоритмическими конструкциями и структурами данных. Массив — цикл со счетчиком, запись с вариантами — оператор выбора... Если честно, это всё, третьего примера у меня нет.
Насколько это полезно сегодня, можно понять по тому, что в PascalABC записи с вариантами вообще не поддерживаются, для компиляции пришлось использовать Free Pascal Compiler (version 3.2.2). В проекте PascalABC на Github можно найти ответ на вопрос "Does PascalABC.NET support Delphi variant records?": "Generally, you should use OOP things for this, like abstract classes and interfaces.", и тут трудно спорить.
Этот небольшой экскурс в историю конечно не отменяет того, что именно педагогические задачи подталкивали автора Pascal выбирать простой и надёжный синтаксис, следовать принципам строгости и последовательности, отказываясь от того, что могло сделать написание кода легче, но затрудняет его понимание и объяснение. Просто принципы так наглядно не покажешь.
А что же у Python? Как ни удивительно, тут тоже есть что назвать. И это странно, но назвать можно те же самые значимые отступы, которые мы ругали в предыдущем разделе. Одной из мотиваций для этого решения у Гвидо ван Россума (автора Python) было обучить программистов правильно форматировать код, соблюдая правило отступов. Правило отступов и в других языках (и в Pascal) является настоятельной рекомендацией, а Ван Россум сделал его обязательным (и для этого — единственным способом выделения блоков в программе).
Чей подход к методике обучения программированию вам больше нравится — каждый может решить самостоятельно.
Популярность, инструменты, библиотеки
Если в предыдущих номинациях я бы отдал победу Pascal, то по части популярности и использования в индустрии Python уверенно лидирует. Широта областей применения Python и разнообразие библиотек не то что по сравнению с Pascal, а в общем зачете языков программирования претендует на первое место. Не буду углубляться в эту тему, такие обзоры легко найти, в целом почти все сферы применения, от машинного обучения до разработки игр. Ну и как следствие — огромное количество информации, включая многочисленные и разнообразные учебные материалы, активное сообщество (или даже насколько, в разных областях), учебные курсы, стажировки, предложения работы и т.д. Инструментарий тоже крайне развит и разнообразен, под любые потребности, от онлайн блокнотов (Jupyter Notebook / Google Colab) до промышленных IDE c элементами ИИ (IntelliJ PyCharm).
У Pascal же всё это поскромнее (мягко говоря). Востребованность в индустрии катастрофически упала, и продолжает снижаться (хотя отдельные вакансии на Delphi ещё встречаются, но редко и за заметно меньшие деньги). Область применения и активность сообщества сосредоточена в основном в сфере образования. Учебной и методической информации тоже много. Интересно отметить, что учебные материалы по Pascal отличаются немного другой направленностью, больше академичностью что ли, от материалов по Python, но и тех и других так много, что найти можно под любой вкус. Инструменты есть, и вроде их достаточно, но даже отличный PascalABC всё-таки до продуктов IntelliJ не дотягивает (хотя зато шустрее и менее прожорливый).
А есть другие варианты?
На Pascal и Python предложения не заканчиваются. Для экономии времени мысленно оценим все языки программирования (какие вспомним) по двум условным шкалам: популярность/"лёгкость" и "мощность" (оба термина надо бы пояснить, но это долго, и для быстрой оценки хватит интуитивного понимания). Интересно, что они, как правило, антагонистичны (либо лёгкий, либо мощный). И с этими оценками обычно сильно коррелирует порог вхождения в язык (начать в лёгком — легко, а в мощном — трудно). Синтаксис лёгких языков часто адаптивный, "толерантный", а у мощных — более строгий (но это не точно). Также с этой дихотомией ассоциируется отчасти высокоуровневые / низкоуровневые языки. Мощные языки обычно ближе к машинной реализации вычислений (что и является источником их мощи), а лёгкие языки оперируют более близкой к человеческому мышлению абстракцией.
С этой точки зрения Python будет более лёгкий (ниже порог вхождения, больше подробностей скрыто за абстракцией, больше толерантности), а Pascal — более мощный (машинный код ближе, порог вхождения выше, больше строгости). Большинство других языков, на которых обучают (и самообучаются) программированию, оказываются либо ещё легче (и разболтаннее), чем Python (JavaScript, PHP, 1С), либо ещё мощнее (и труднее), чем Pascal (С++, C#, Java).
Но есть язык программирования, который на мой взгляд оказывается между нашими реперами. Он легче и популярнее, чем Pascal, и при этом строже и мощнее, чем Python. И Именно он будет неожиданной и свежей идеей для этой статьи. Это Kotlin (или Котлин, это вообще-то русское слово, это название острова, на котором находится город Кронштадт). Про Kotlin я могу рассказывать очень долго (это мой любимый язык программирования), но это сильно выйдет за рамки текущей темы. Так что сразу приведу резюме (похожее на рекламный слоган, но что поделать):
Если у вас сложилось обоснованное, "выстраданное" мнение, как что-то в языке должно быть сделано — скорее всего, в Kotlin это сделано именно так.
Если вы страдали от отсутствия какой-то возможности в языке программирования — скорее всего, в Kotlin эта возможность есть.
Ну и давайте попробуем перевести наш тестовый код, которым мы уже испытали Python и Pascal:
import kotlin.math.pow
fun Boolean.toInt(): Int = if (this) 1 else 0
fun main() {
var x: Int = 1
var b: Boolean = true
// Типы обязательны и фиксированы, даже если не указаны явно
println(b + x)
// Ошибка компиляции: Unresolved reference.
// None of the following candidates is applicable because of receiver type mismatch:
// public inline operator fun BigDecimal.plus(other: BigDecimal): BigDecimal ...
// Довольно многословно, но объясняет,
// что оператора plus для сложения Boolean и Int нет
b = 1
// Ошибка компиляции:
// The integer literal does not conform to the expected type Boolean
b = b + 2
// Ошибка компиляции: Unresolved reference...
// Снова долго, но смысл тот же, подходящего оператора plus нет
println(false.toInt().toFloat().pow(false.toInt()).toInt() == true.toInt())
// Выводит "true",
// пришлось написать функцию Boolean.toInt, готовой нет
// (потому что она никому на самом деле не нужна).
// Но это было просто, и с ней удалось в точности воспроизвести пример на Python.
// Выглядит запутанно (и так и есть), но если внимательно читать, то понятно,
// что же там происходит, и почему ответ именно такой.
var x: BigInteger
// Ошибка компиляции: Conflicting declarations
var y = 2.toBigInteger().pow(54) + 1.toBigInteger()
println((y.toFloat() + 1.0).toBigDecimal() < y.toBigDecimal())
// Выводит "True", все преобразования выполнены явно
}
Всё получилось, как и у Pascal, правда текст сообщений об ошибках подкачал. Да и в целом наверно чувствуется, что технология скорее с расчетом на профессионалов, а не на студентов.
Что ж, давайте и сортировку попробуем перевести:
import kotlin.random.Random
fun bubble(a: Array<Int>) {
val n = a.size
var i = 0
while (i < n - 1) {
var j = 0
while (j < n - 1 - i) {
if (a[j] > a[j + 1]) {
Pair(a[j + 1], a[j]).apply {
a[j] = first
a[j + 1] = second
}
}
j += 1
}
i += 1
}
}
fun main() {
val a = Array<Int>(10) { Random.nextInt(99) }
bubble(a)
println(a.contentToString())
}
Как и обещано, мощность и строгость Pascal вместе с лаконичностью и богатством Python. Даже обмен значением сделал как в Python, через построение и деструкцию пары (только здесь это явно описано в коде). И массив инициализируется через выражение, только интуитивно понятно, где тут размер массива, а где код инициализации значений.
Что же до популярности, областей применения, сообщества, библиотек и инструментов, всё это примерно как у Java (ближайшего родственника кстати), только ещё и продолжает расти.
Выводы
Что ж, получилось обсудить, по каким параметрам вообще стоит сравнивать языки, на которых собираемся обучать программированию. Сравнили пару основных кандидатов примерно по намеченному плану, и даже предложили свежее "Соломоново решение". Конечно, однозначного вывода, какой язык самый подходящий для обучения, сделать нельзя. Серебреной пули не бывает, многое зависит от целей обучения, аудитории, да и от преподавателя. Но надеюсь, что теперь ваш выбор будет чуть более взвешенным.
bighorik
Категорически поддерживаю.
Сам являюсь преподавателем в it-ориентированном колледже. Веду шарпы у студентов 3 курса со специализацией .net. На первом курсе им давали питон и js , после чего некоторые не могут никак понять, на кой в коде всякие int и bool, как работает обычный for , почему они не могут сделать int a = console.readline() и тп. Надеюсь, в будущем смогу повлиять на программу.
Мне с минимальными знаниями по шарпу в свое время было легко вкатиться в js, но я даже представить себе не могу , какого труда бы мне это стоило, выстройся мой путь наоборот.
vadimr
Что характерно, в общем-то они правы. Тут вопрос в том, что является целью образования: передача устоявшихся предрассудков или развитие когнитивных способностей.
А как, кстати, вы отвечаете на вопрос, на кой в коде int и bool? Для борьбы с ошибками? Так это сложно объяснить людям, которые до этого прекрасно обходились без них. Я своим студентам честно объясняю, что это религиозная традиция, возникшая во времена, когда так было проще написать эффективный транслятор, а также что некоторые люди считают, что это сильно помогает в борьбе с ошибками.
fixikus
Все просто объясняется. Программирование основано на логике. Первый закон логики: иметь не одно значение — значит не иметь ни одного значения; если же у слов нет значений, тогда утрачена всякая возможность рассуждать друг с другом, а в действительности — и с самим собой; ибо невозможно ничего мыслить, если не мыслить что-нибудь одно. Статическая типизация это как раз "реализация" этого закона.
vadimr
Эк вы глубоко копнули. Однако про типы переменных в этом законе ничего нет. А их значения единственны.
fixikus
Ясен пень, этот закон Аристотель вывел.
Смысл в том, что должна быть определенность. int - базовый целочисленный тип; bool - это примитивный тип данных в программировании, который может принимать только два значения: истина и ложь; и т. д. Типы не равны друг другу, но можно преобразовывать из один в другой. Статическая типизация позволяет вам следить за корректностью хода ваших мыслей и сразу по рукам бить, если что не так. Динамическая типизация позволяет вам говнокодить и получать ошибки в рантайме.
vadimr
Очень маловероятно, чтобы в реальной программе ошибка возникла бы именно на уровне несовместимости int с bool. Тут уж надо или формально выводить настоящие категориальные типы, как в Haskell или даже Coq, или не делать вид, что всё хорошо.
Я очень много программировал на Паскале в своей жизни (такое время было, когда это было модно), и в целом этот язык мне скорее нравился, но я не могу припомнить, чтобы система статической строгой типизации Паскаля когда-либо нашла мне реальную ошибку в программе, а не мнимую (то есть не сводящуюся к тому, что в этом месте по синтаксису нужно вставить явное преобразование типов).
Alex-ZiX
Присваиваешь ты значение одной переменной другой переменной. Первая 32 бита, вторая 64. Всё присваивается и работает пока во вторую не запишут значение, которое не влезет в первую. А компилятор на этапе компиляции тебе уже даст подсказку, что может такая ситуация случиться.
funca
Для чего вообще в языке программирования высокого уровня нужны два разных типа целых? Выглядит как решение проблемы, которой в принципе не должно существовать.
В бизесовых приложениях чаще делают ошибки другого рода. Например, когда пользователь вводит невалидный номер телефона, или пытается списать с баланса склада больше, чем там лежит.
Vladimir_Zaitsev Автор
Два? Обычно их гораздо больше.
Идея автоматизировать выбор формата данных - понятна (полезность для обучения пока отставим). Но приводит к огромному оверхеду из-за избыточности (как неотменяемая длинная арифметика в Python). И к постоянным перекодировкам, со всеми вытекающими и по производительности, и по надёжности.
funca
Это действительно нужно для учёного языка? По-моему важнее интерактивность, релевантный набор абстракций изучаемым темам. Доступный API, интроспекция, чтобы абстракцию можно было крутить и разбирать на части как игрушку. А также адекватные, объясняющие сообщения об ошибках.
Я не фанат, но в том же питоне есть целые в смысле математики, и ctypes для работы с С-подобными структурами. В этом конструкторе подкупает возможность интерактивно "пощупать" и арифметику с фиксированной разряднотстью, и размеры структур и их отдельных полей, и работу с указателями и т.п. Можно поиграться с выравниванием, endian, и прочими прелестями, которые обычно зашиты в дебрях компиляторов и доступны в ощущениях разве что через дисассемблер. Синтаксис, правда, местами своеобразный.
Vladimir_Zaitsev Автор
Про действительно ли нужно сразу вводить разные целые типы данных - дискуссионный вопрос, но могу по опыту сказать, что это точно можно, и не создаёт заметной ступеньки в обучении. Н сильно сложнее, чем просто концепция типов данных. Может даже помочь пониманию.
А вообще - было бы прикольно чери-пикать отдельные дизайн-решения из разных языков, жаль что такого нет.
Smerig
ну вот как понадобится это число сохранить, к примеру, в БД, так сразу голова начнет работать, что выбрать. Можно вообще в строку перевести, по идее.
funca
База, кстати, здесь не самый удачный пример: на границе слоев должен быть полноценный парсинг входящих данных, в рантайме, а не статический тайпчекинг. Мы же не знаем что там снаружи к нам прилетит.
Часто так и есть. Навороты, типа LINQ, могут скрывать от работчика подобные нюансы, реализуя парсинг за сценой - показывая в коде лишь переменные да типы. Это удобно, не спорю. Но у новичка такие неявности могут создать обманчивое впечатление, будто за него все ошибки ловит компилятор.
Smerig
извините, не всегда получается выражать мысли четко :) Имеется в виду именно типы в БД. Если все вдруг будет вместо bit int64 к примеру
funca
Ну мы вообще довольно далеко ушли от начальногой темы).
Serjone
Не обязательно в бд. Как только начинаешь работать со сторонними сервисами, файлами, приложениями и памятью, так может оказаться, что твой универсальный тип переменной туда не подходит и нужно брать конкретные типы, которые учитывал писатель стороннего приложения. В делфи есть тип variant - бери и пихай туда что угодно, но тогда как раз и всплывают те накладные расходы, которые будут каждый раз определять, а что у нас содержится в переменной и как с этим работать.
Alex-ZiX
Как минимум для того, что один тип требует для хранения в два раза меньше памяти, чем другой. Представим обычную картинку. Чтобы её хранить, каждая точка в ней кодируется 4 байтами - RGB + альфа-канал. По вашей логике зачем использовать там целочисленный байт - давайте сразу брать UInt64. И размер нашей картинки вырастет в 8 раз. Это как пример и таких примеров огромное количество.
А при чём тут неверный пользовательский ввод вообще непонятно - такие задачи решаются через регулярные выражения. Это вообще разные типы ошибок.
funca
Думаю, что эта тема была близка каждому во времена Вирта, где всем должно было хватать 640Кб . Но сейчас она мне кажется скорее специфичной.
Ни в коем случае не умоляю важности понимания того как информация кодируется и представляется в компьютере, и откуда в программах берутся ошибки переполнения. Но это вряд-ли то, обо что нужно вынуждать каждый раз спотыкаться на протяжении всего курса обучения.
Smerig
представляете, если все картинки вмиг вырастут в объеме в 8 раз. Этот объем для записи потребует в 8 раз больше времени. А если у вас тысяча фоточек. Раньше вы их за минуту копировали, а сейчас за 8. И это мы только про фоточки. А еще винда потяжелеет, весь код, базы данных, голос по мобильной сети передать тоже дольше. Эти числа - они везде, не только в картинках. Контрольный: уже не RTX 5070ti с 16 гигами GDDR7 памятью надо, а 128 гигов GDDR7. Интересно, сколько такая карточка стоить будет...
Kreatifchk
Сейчас в нейросетях часто используется тип fp16 для экономии памяти, а тут такое предложение отказаться от 32 битных переменных
funca
Я считаю, что вы могли бы здесь показать сильные аргументы с позиций обучения. Однако в качестве примеров почему-то приводите задачи ввода-вывода, где статическая типизация не имеет существенного значения. Вы же не предлагаете на самом деле раскидывать нюансы работы с внешним миром по всей программе, вместо того чтобы обучать отрабатывать сериализацию/десериализацию данных на границах слоев?
masterthemac
Можно, конечно, сделать числа в языке только одного типа, условно, хранить как строки произвольной длины. И использовать длинную арифметику для работы с ними. Со всем сопутствующим оверхедом.
Насколько программы замедлятся из-за этого - неясно. Полагаю что существенно.
vadimr
Ну питон и некоторые другие языки так работают.
masterthemac
Это была попытка донести аргумент, что в итоге программы выполняются не в мире концепций, а на реальном железе, у которого есть железные же ограничения.
Работать с числами в строковом представлении можно, но для процессора придется все-таки их переводить в те представления чисел, которые поддерживаются в архитектуре вычислений этого процессора.
И все это придется делать за счет дополнительных приседаний, которых можно попытаться избежать, если в языке есть типы данных, нативные для оборудования.
vadimr
Конечно, но надо смотреть, когда и насколько это оправдано.
Для сложных вычислительных задач, когда надо выжимать последние наносекунды из обработки чисел, конечно, надо использовать адекватное машинное представление. Но там и Фортран выучить бы заодно неплохо.
А в рядовой повседневной практике арифметика неограниченной точности может быть вполне оправдана.
funca
Зачем вам числа в языке только одного типа? От языка требуется возможность определять свои domain-specific абстракции, а не спотыкаться постоянно о встроенные машино-специфичные типы.
Если работаешь с цветами, то практичнее определить тип Color с нужным набором операций, оставив нюансы низкоуровневого представления за кадром. Среди этих операций наверняка будет работа с цветовыми пространствами, масками, отдельными компонентами. А вот деления или взятия корня n-й степени быть не должно, поскольку они здесь не имеют смысла.
Как находить и строить подходящие абстракции для предметной области, - вот этому искусству нужно учиться, - а не таскать специфику упаковки данных в памяти через все приложение. Я серьёзно не вижу повода для гордости от того, что в языке высокого уровня, машино-специфичные типы выставлены наружу на уровне языка. Все это может и должно жить в библиотеке.
Zalechi
Интересная мысль.
masterthemac
Вроде бы я и не настаивал, что числа должны быть абстрактно-универсальными.
Я как раз за то, чтобы не забывать, глядя на небоскребы, что в основе все-таки конкретные сталь и бетон, а не некий универсальный "строительный материал" знания о котором строителям этих небоскребов не важны.
Вроде бы никто и не мешает определять. Просто не на уровне самого языка, а пользуясь им. На то это и язык.
Не могу припомнить языки явно страдающие от поддержки встроенных типов. Но может такие и есть, просто я не в курсе.
На мой взгляд "работа с цветом", "работа с документами", "работа с авиабилетами" это не задача языка. Это задачи конкретных библиотек/фреймворков.
Например, есть задача "описать вкус кофе".
На русском языке можно миллионом разных способов описать вкус кофе, который вы выпили на завтрак.
Сам язык при этом не имеет средств для истинной передачи вкуса, чтобы читатель ощутил ровно то же, что и вы.
Однако это не мешает пользоваться языком для описания вкуса, причем так, что читатель поймет что же писатель хотел до него донести.
Так же и на языках программирования высокого уровня можно описать нужные алгоритмы и при этом не требуется, чтобы язык поддерживал именно этот конкретный алгоритм или именно под него был разработан.
Предметные домены, предметные же типы данных и связанные со всем этим абстрации должны жить в библиотеках, соглашусь.
Возможно, кстати, что вы видите какую-то принципиальную проблему в языках с определенными типами, но от меня эта проблема как-то ускользает.
IUIUIUIUIUIUIUI
Статическая типизация как раз помогает
доказатьубедиться, что на границах слоя сериализация/десериализация была выполнена, и что все части слоя согласны с тем, что именно должно было быть (де)сериализовано.Serjone
А потом мы получаем приложения вроде ардуино иде на 200 мегабайт и к нему накладных библиотек ещё в полтора раза больше. А что, удобно же писать на фреймворках, писанных на фреймворках, можно не заморачиваться о памяти. Ну, не будет приложуха на диск влазить, купите новый диск. Теперь в оперативу не влазит? Вот новая в магазине нидорага. Что, нужна новая материнка, проц, блок питания? Зачем этот архаизм, берите макбук за 300к и не парьтесь.
randomsimplenumber
Прекрасно. Где можно посмотреть аналог arduino ide без фреймворков?
andyblaster
Ещё можно все кушать ложкой, непонятно, зачем вообще вилки тогда придумали?
vadimr
Большая часть человечества вообще кушает палочками, так что аналогия не очень ясно просматривается.
andyblaster
Из аналогии с ложками и палочками, на самом деле, много выводов можно сделать, если вы не будете останавливаться в анализе аналогий.
Ключевой вывод в том, что человек, предложивший единственный числовой тип, только-только проникся идеей безусловной фиксированной размерности, и, естественно, имея в руках такой молоток, горит желанием видеть вокруг гвозди.
Где-то эта идея идея полезна, если значимое снижение когнитивной сложности оправдывает последствия роста потребления ресурсов, но как будто бы единственный числовой тип - это не тот случай, как выше уже любезно объяснили.
Zalechi
Как для чего… так работают транзисторы. У них два состояния, что волшебным образом соврадает с булевой математикой, что в свою очередь дает возможность производить более сложные математичсекие вычисления.
Языки программирования создавали из этих соображений и ограниченности вычислительных способностей современности. Потом в девяностых назрела концепция абстрагирования, так сказать, ибо индустрия железа сильно стрельнула. Плюс интернет появился. Независимые академики поспользовались железной рефолюцией и придумали не мало полезных вещей, языков программирования, ядер операционных систем…
Никто не виноват и все виноваты одновременно.
randomsimplenumber
Ахаха. Пресловутый аппарат Therac. Одно из решений: uint8 кастовался в bool - ничего страшного, да. Все так делают. Но в другом месте он инкрементировался, и иногда переполнялся.
vadimr
Если он именно кастовался, то статическая типизация ничем не помогла. А вот динамическая, кстати, помогла бы в момент первого инкремента.
Так что ваш аргумент играет против того, что вы, по всей видимости, хотели им доказать.
randomsimplenumber
Няп в одном месте было
if (x)
В другом
x++
Программист решил, что так взводить флажок нормально. Тут никакая типизация не спасет.
Serjone
Странные вещи пишете. У меня были в реальной программе ошибки на уровне несовместимости bool и boolean и ByteBool. Разница в размере переменной, и она в том месте была неочевидна. То есть программа прекрасно работала до тех пор, пока перед внесениием в булевую переменную не происходило каких-то других действий. Стоило сложить пару чисел выше и всё, байты съезжали и переменная ломалась. Да, там работа была через указатели. При этом программа прекрасно работала некоторое время, пока не понадобилось добавить какую-то мелочь.
vadimr
Я именно про это и написал - самому себе созданные сложности, лечащиеся прямым кастингом.
vadimr
Если бы, допустим, язык программирования, на котором вы писали свою программу, имел бы динамическую типизацию, то переменные автоматически предоставляли бы значениям столько памяти, сколько им нужно.
randomsimplenumber
О, с++ сам выделяет памяти сколько нужно. Без динамической типизации.
vadimr
Он выделяет, сколько нужно памяти переменной, а не её значению.