В первом уравнении мы вернём ноль, если второй аргумент окажется нулём, а во втором мы за каждый
конструктор Succво втором аргументе прибавляем к результату первый аргумент. В итоге, после вычисле-
ния a *b мы получим аргумент a сложенный b раз. Это и есть умножение. При этом мы воспользовались
операцией сложения, которую только что определили. Посмотрим на схему вычисления:
3*2 → 3 + (3*1) → 3 + (3 + (3*0)) → 3 + (3+0) → 3+3 →
1 + (3+2) → 1 + (1 + (3+1)) → 1 + (1 + (1 + (3+0))) →
1 + (1 + 1 + 3) → 1 + (1 + (1 + (1 + (1 + (1 + 0))))) → 6
Операции abs и signum
Поскольку числа у нас положительные, то методы abs и signum почти ничего не делают:
abs
x
=x
signum Zero = Zero
signum _
= Succ Zero
Арифметика | 33
Перегрузка чисел
Остался последний метод fromInteger. Он конструирует значение нашего типа из стандартного:
fromInteger 0 = Zero
fromInteger n = Succ(fromInteger (n -1))
Зачем он нужен? Попробуйте узнать тип числа 1 в интерпретаторе:
*Nat> :t 1
1 ::( Numt) =>t
Интерпретатор говорит о том, тип значения 1 является некоторым типом из класса Num. В Haskell обозна-
чения для чисел перегружены. Когда мы пишем 1 на самом деле мы пишем (fromInteger (1 ::Integer)).
Поэтому теперь мы можем не писать цепочку Succ-ов, а воспользоваться методом fromInteger, для этого
сохраним определение экземпляра для Numи загрузим обновлённый модуль в интерпретатор:
[1 of1] Compiling Nat
( Nat.hs, interpreted )
Ok, modules loaded : Nat.
*Nat>7 :: Nat
Succ( Succ( Succ( Succ( Succ( Succ( Succ Zero))))))
*Nat>(2 +2) :: Nat
Succ( Succ( Succ( Succ Zero)))
*Nat>2 *3 :: Nat
Succ( Succ( Succ( Succ( Succ( Succ Zero)))))
Вы можете убедиться насколько гибкими являются числа в Haskell:
*Nat>(1 +1) :: Nat
Succ( Succ Zero)
*Nat>(1 +1) :: Double
2.0
*Nat>1 +1
2
Мы выписали три одинаковых выражения и получили три разных результата, меняя объявление типов. В
последнем выражении тип был приведён к Integer. Это поведение интерпретатора по умолчанию. Если мы
напишем:
*Nat> letq =1 +1
*Nat> :t q
q :: Integer
Мы видим, что значение q было переведено в Integer, это происходит лишь в интерпретаторе, если такая
переменная встретится в программе и компилятор не сможет определить её тип из контекста, произойдёт
ошибка проверки типов, компилятор скажет, что он не смог определить тип. Помочь компилятору можно,
добавив объявление типа с помощью конструкции (v :: T).
Посмотрим ещё раз на определение экземпляра Numдля Natцеликом:
instance Num Nat where
( +) a Zero
=a
( +) a ( Succb) = Succ(a +b)
( *) a Zero
= Zero
( *) a ( Succb) =a +(a *b)
fromInteger 0 = Zero
fromInteger n = Succ(fromInteger (n -1))
abs
x
=x
signum Zero = Zero
signum _
= Succ Zero
negate _ = error”negate is undefined for Nat”
34 | Глава 2: Первая программа
Класс Fractional. Деление
Деление определено в классе Fractional:
*Nat>:m Prelude
Prelude> :i Fractional
class Numa => Fractionala where
( /) ::a ->a ->a
recip ::a ->a
fromRational :: 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, то к нему подспудно будет применена
функция fromInteger, а если 2.0, то будет применена функция fromRational.
Стандартные числа
В этом подразделе мы рассмотрим несколько стандартных типов для чисел в Haskell. Все эти числа явля-
ются экземплярами основных численных классов. Тех, которые мы рассмотрели, и многих-многих других.
Читать дальше