Ответ 1
Верно, что выразительность системы типа Haskell побуждает пользователей назначать точные типы сущностям, которые они определяют. Однако опытные Haskellers с готовностью признают, что нужно балансировать между точностью конечного типа (что, кроме того, не всегда достижимо с учетом текущих ограничений системы типа Haskell) и удобством. Короче говоря, точные типы полезны только для точки. Помимо этого, они часто просто заставляют лишнюю бюрократию практически не выигрывать.
Позвольте проиллюстрировать проблему с примером. Рассмотрим факторную функцию. Для всех n
больше 1 факториал n
является четным числом, а факториал 1 не очень интересен, поэтому пусть его игнорируют. Поэтому, чтобы убедиться, что наша реализация факториальной функции в Haskell верна, может возникнуть соблазн ввести новый числовой тип, который может представлять только целые числа без знака:
module (Even) where
newtype Even = Even Integer
instance Num Even where
...
fromInteger x | x `mod` 2 == 0 = Even x
| otherwise = error "Not an even number."
instance Integral Even where
...
toInteger (Even x) = x
Мы запечатываем этот тип данных внутри модуля, который не экспортирует конструктор, чтобы сделать его абстрактным, и сделать его экземпляром всех соответствующих классов классов, которые Int
является экземпляром. Теперь мы можем дать факториалу следующую сигнатуру:
factorial :: Int -> Even
Тип factorial
уверен более точен, чем если бы мы только сказали, что он возвращает Int
. Но вы обнаружите, что определение factorial
с таким типом действительно очень раздражает, потому что вам нужна версия умножения, которая умножает (четный или нечетный) Int
на Even
и производит и Even
. Что еще, вам может потребоваться ввести посторонние вызовы toInteger
в результате вызова factorial
в код клиента, что может стать значительным источником беспорядка и шума для небольшого усиления. Кроме того, все эти функции преобразования могут потенциально негативно повлиять на производительность.
Другая проблема заключается в том, что при введении нового, более точного типа вам часто приходится дублировать всевозможные библиотечные функции. Например, если вы вводите тип List1 a
непустых списков, тогда вам придется переопределить многие из функций, которые уже предоставляет Data.List
, но только для [a]
. Несомненно, тогда можно использовать эти функции для методов класса ListLike
. Но вы быстро закончите со всеми типами классов adhoc и другим шаблоном, с еще большим выигрышем.
Наконец, нельзя считать Word
неподписанным вариантом Int
. Отчет Haskell оставляет неопределенный фактический размер Int
и гарантирует только то, что этот тип должен быть способен представлять целые числа в диапазоне [- 2 29 2 29 - 1]. Говорят, что тип Word
предоставляет целые числа без знака неопределенной ширины. Не гарантируется, что в любой соответствующей реализации ширина a Word
соответствует ширине a Int
.
Несмотря на то, что я делаю упор на чрезмерное распространение типов, я подтверждаю, что введение типа Natural
натуральных может быть приятным. В конечном счете, однако, должен ли Haskell иметь выделенный тип для натуральных чисел, в дополнение к Int
, Integer
и различным типам Word*
, в значительной степени зависит от вкуса. И нынешнее положение дел, вероятно, в значительной степени является случайностью истории.