Определим экземпляр для чисел Пеано, но давайте сначала разберём функции по частям.
Сложение
Начнём со сложения:
instance Num Nat where
(+
) a Zero=
a(+
) a (Succ b) = Succ (a + b)Первое уравнение говорит о том, что, если второй аргумент равен нулю, то мы вернём первый аргумент
в качестве результата. Во втором уравнении мы “перекидываем” конструктор Succ
из второго аргумента запределы суммы. Схематически вычисление суммы можно представить так:
3+2
1 + (1 + 3
)Все наши числа имеют вид 0 или 1+
составить число в этом же виде, для этого мы последовательно перекидываем $(1+) в начало выражения из
второго аргумента.
Вычитание
Операция отрицания не имеет смысла, поэтому мы воспользуемся специальной функцией error ::
String ->
a, она принимает строку с сообщением об ошибке, при её вычислении программа остановит-ся с ошибкой и сообщение будет выведено на экран.
negate _ = error
”negate is undefined for Nat”Умножение
Теперь посмотрим на умножение:
(*
) a Zero= Zero
(*
) a (Succ b) = a + (a * b)В первом уравнении мы вернём ноль, если второй аргумент окажется нулём, а во втором мы за каждый
конструктор Succ
во втором аргументе прибавляем к результату первый аргумент. В итоге, после вычисле-ния a *
b мы получим аргумент a сложенный b раз. Это и есть умножение. При этом мы воспользовалисьоперацией сложения, которую только что определили. Посмотрим на схему вычисления:
3*2
1 + (3+2)
1 + (1 + 1 + 3
)Операции abs и signum
Поскольку числа у нас положительные, то методы abs и signum почти ничего не делают:
abs
x
=
xsignum Zero = Zero
signum _
= Succ Zero
Арифметика | 33
Перегрузка чисел
Остался последний метод fromInteger. Он конструирует значение нашего типа из стандартного:
fromInteger 0 = Zero
fromInteger n = Succ
(fromInteger (n-1))Зачем он нужен? Попробуйте узнать тип числа 1 в интерпретаторе:
*Nat> :
t 11 ::
(Num t) => tИнтерпретатор говорит о том, тип значения 1 является некоторым типом из класса Num
. В Haskell обозна-чения для чисел перегружены. Когда мы пишем 1 на самом деле мы пишем (fromInteger (1::Integer
)).Поэтому теперь мы можем не писать цепочку Succ
-ов, а воспользоваться методом fromInteger, для этогосохраним определение экземпляра для Num
и загрузим обновлённый модуль в интерпретатор:[1 of
1] Compiling Nat( Nat.
hs, interpreted )Ok
, modules loaded: Nat.*Nat>
7 :: NatSucc
(Succ (Succ (Succ (Succ (Succ (Succ Zero))))))*Nat>
(2 + 2) :: NatSucc
(Succ (Succ (Succ Zero)))*Nat>
2 * 3 :: NatSucc
(Succ (Succ (Succ (Succ (Succ Zero)))))Вы можете убедиться насколько гибкими являются числа в Haskell:
*Nat>
(1 + 1) :: NatSucc
(Succ Zero)*Nat>
(1 + 1) :: Double2.0
*Nat>
1 + 12
Мы выписали три одинаковых выражения и получили три разных результата, меняя объявление типов. В
последнем выражении тип был приведён к Integer
. Это поведение интерпретатора по умолчанию. Если мынапишем:
*Nat> let
q = 1 + 1*Nat> :
t qq :: Integer
Мы видим, что значение q было переведено в Integer
, это происходит лишь в интерпретаторе, если такаяпеременная встретится в программе и компилятор не сможет определить её тип из контекста, произойдёт
ошибка проверки типов, компилятор скажет, что он не смог определить тип. Помочь компилятору можно,
добавив объявление типа с помощью конструкции (v :: T
).Посмотрим ещё раз на определение экземпляра Num
для Nat целиком:instance Num Nat where
(+
) a Zero=
a(+
) a (Succ b) = Succ (a + b)(*
) a Zero= Zero
(*
) a (Succ b) = a + (a * b)fromInteger 0 = Zero
fromInteger n = Succ
(fromInteger (n-1))abs
x
=
xsignum Zero = Zero
signum _
= Succ Zero
negate _ = error
”negate is undefined for Nat”34 | Глава 2: Первая программа
Класс Fractional. Деление
Деление определено в классе Fractional
:*Nat>:
m PreludePrelude> :
i Fractionalclass Num
a => Fractional a where(/
) :: a -> a -> arecip ::
a -> afromRational :: Rational ->
a-- Defined in ‘GHC.Real’
instance Fractional Float
-- Defined in ‘GHC.Float’instance Fractional Double
-- Defined in ‘GHC.Float’Функция recip, это аналог negate для Num
. Она делит единицу на данное число. Функция fromRationalстроит число данного типа из дробного числа. Если мы пишем 2, то к нему подспудно будет применена