О чём молчат авторы «Hello, World!»-ов
Обычно адепты нового Того-самого-лучшего-языка пишут свои рекламные мини-программы для обучения и продвижения примерно так:
ageOfBob = 0
ageOfMary = 0
print("Input Bob's age: ")
read(ageOfBob)
print("Input Marry's age: ")
read(ageOfMary)
// Do the math
if (ageOfBob > ageOfMary)
print("Bob is older than Mary by ", ageOfBob - ageOfMary, " years")
else if (ageOfBob < ageOfMary)
print("Mary is older than Bob by ", ageOfMary - ageOfBob, " years")
else
print("Mary and Bob are of the same age")
Предполагается, что новичку, заинтересовавшемуся языком, понравится очередной упрощенный вариант C-подобного синтаксиса с автоматическим приведением типов, без лишних точек с запятыми, простыми именами встроенных функций и прочее.
Если этот новичок и вправду заинтересуется Тем-самым-лучшем-языком, то к тому моменту, когда ему придётся писать взрослые программы в соответствии с рекомендациями по написанию надёжного кода для серьёзных клиентов или серьёзного работодателя, отступать уже будет поздно:
const MAX_PERSON_AGE = 120
const MIN_PERSON_AGE = 1
int getAge(string name) {
age = 0
print("Input ", name, "'s age: ")
read(age)
if (age < MIN_PERSON_AGE or age > MAX_PERSON_AGE)
throw IncorrectAgeInputException
else
return age
}
try {
ageOfBob = getAge("Bob")
ageOfMary = getAge("Mary")
} catch (IncorrectAgeInputException) {
print("You're doing it wrong!")
}
// Do the math
...
Итого лёгким движением руки четыре строки кода ввода числа превратились в определение пары констант (потому как магические числа в коде — плохо) и функцию (потому как Don’t Repeat Yourself), которая генерирует исключения, которые также нужно обрабатывать. А если ещё вспомнить, что функцию getAge
ещё нужно покрыть хотя бы парой unit-тестов…
Такой детский пример на деле легко обобщается на широкий класс задач, связанных с обработкой внешнего ввода, когда программа получает заведомо неизвестные значения, которые могут быть чем угодно.
Теперь посмотрим, как возможности строгой типизации могут помочь решить такую задачу.
Перепишем нашу повзрослевшую программу на Ada. В Ada уже с 1983-го года есть та самая фича «если оно компилируется, значит оно работает», которой теперь рекламируют Haskell и Rust. Также программы на Ada компилируются в native-код и могут работать в том числе на микроконтроллерах в реальном времени, не на много уступая языку С по скорости выполнения. Впрочем мы отвлеклись…
with ada.text_io, ada.integer_text_io, ada.io_exceptions;
use ada.text_io;
procedure main is
type Age is range 1 .. 120;
package io is new ada.text_io.integer_io(num => Age);
ageOfBob, ageOfMary : Age;
begin
put_line("Input Bob's age: ");
io.get(ageOfBob);
put_line("Input Mary's age: ");
io.get(ageOfMary);
-- Do the math
if ageOfBob > ageOfMary then
put_line("Bob is older than Mary by" & Age'Image(ageOfBob - ageOfMary) & " years");
elsif ageOfBob < ageOfMary then
put_line("Mary is older than Bob by" & Age'Image(ageOfMary - ageOfBob) & " years");
elsif ageOfBob = ageOfMary then
put_line("Mary and Bob are of the same age");
end if;
exception
when ada.io_exceptions.Data_Error =>
put_line("You're doing it wrong!");
when others => null;
end main;
По сравнению с самым простым первым вариантом этой программы был лишь добавлен новый тип Age
с явно заданным диапазоном значений:
type Age is range 1 .. 120;
Также был подключен пакет ada.text_io.integer_io
, параметризованный этим типом:
package io is new ada.text_io.integer_io(num => Age);
Теперь при вызове функции io.get(ageVar)
, где ageVar
— переменная типа Age
, будет проверяться введённое пользователем число и в том случае, если оно не соответствует своему типу Age
, будет генерироваться исключение Data_Error
.
Часто можно услышать что язык Ada сложный, многословный и трудный для изучения. Здесь приведён пример того, как «лёгкие» на первый взгляд языки просто перекладывают часть работы на программиста, в то время как «сложные» языки, на изучение фишек которых может и вправду понадобиться больше времени, позволяют сэкономить время на отладке и проверке при реальной разработке.