class Eqa where
( ==) ::a ->a -> Bool
( /=) ::a ->a -> Bool
a ==b =not (a /=b)
a /=b =not (a ==b)
Появились две детали, о которых я умолчал в предыдущей главе. Это две последние строчки. В них
мы видим определение ==через /=и наоборот. Это определения методов по умолчанию. Такие определения
дают нам возможность определять не все методы класса, а лишь часть основных, а все остальные мы получим
автоматически из определений по умолчанию.
Казалось бы почему не оставить в классе Eqодин метод а другой метод определить в виде отдельной
функции:
class Eqa where
( ==) ::a ->a -> Bool
( /=) :: Eqa =>a ->a -> Bool
a /=b =not (a ==b)
Так не делают по соображениям эффективности. Есть типы для которых проще вычислить /=чем ==.
Тогда мы определим тот метод, который нам проще вычислять и второй получим автоматически.
Набор основных методов, через которые определены все остальные называют минимальным полным опре-
делением (minimal complete definition) класса. В случае класса Eqэто метод ==или метод /=.
Мы уже вывели экземпляр для Eq, поэтому мы можем пользоваться методами ==и /=для значений типа
Nat:
*Calendar> :l Nat
[1 of1] Compiling Nat
( Nat.hs, interpreted )
Ok, modules loaded : Nat.
*Nat> Zero == Succ( Succ Zero)
False
it :: Bool
*Nat> Zero /= Succ( Succ Zero)
True
it :: Bool
Класс Num. Сложение и умножение
Сложение и умножение определены в классе Num. Посмотрим на его определение:
*Nat> :i Num
class( Eqa, Showa) => Numa where
( +) ::a ->a ->a
( *) ::a ->a ->a
( -) ::a ->a ->a
negate ::a ->a
abs ::a ->a
signum ::a ->a
fromInteger :: Integer ->a
-- Defined in GHC.Num
Методы ( +), ( *), ( -) в представлении не нуждаются, метод negate является унарным минусом, его можно
определить через ( -) так:
32 | Глава 2: Первая программа
negate x =0 -x
Метод abs является модулем числа, а метод signum возвращает знак числа, метод fromInteger позволяет
создавать значения данного типа из стандартных целых чисел Integer.
Этот класс устарел, было бы лучше сделать отельный класс для сложения и вычитания и отдельный
класс для умножения. Также контекст класса, часто становится помехой. Есть объекты, которые нет смысла
печатать но, есть смысл определить на них сложение и умножение. Но пока в целях совместимости с уже
написанным кодом, класс Numостаётся прежним.
Определим экземпляр для чисел Пеано, но давайте сначала разберём функции по частям.
Сложение
Начнём со сложения:
instance Num Nat where
( +) a Zero
=a
( +) a ( Succb) = Succ(a +b)
Первое уравнение говорит о том, что, если второй аргумент равен нулю, то мы вернём первый аргумент
в качестве результата. Во втором уравнении мы “перекидываем” конструктор Succиз второго аргумента за
пределы суммы. Схематически вычисление суммы можно представить так:
3+2 → 1 + (3+1) → 1 + (1 + (3+0))
1 + (1 + 3) → 1 + (1 + (1 + (1 + (1 + 0)))) → 5
Все наши числа имеют вид 0 или 1+ n , мы принимаем на вход два числа в таком виде и хотим в результате
составить число в этом же виде, для этого мы последовательно перекидываем $(1+) в начало выражения из
второго аргумента.
Вычитание
Операция отрицания не имеет смысла, поэтому мы воспользуемся специальной функцией error ::
String ->a, она принимает строку с сообщением об ошибке, при её вычислении программа остановит-
ся с ошибкой и сообщение будет выведено на экран.
negate _ = error”negate is undefined for Nat”
Умножение
Теперь посмотрим на умножение:
( *) a Zero
= Zero
( *) a ( Succb) =a +(a *b)
Читать дальше