Для этого обычно есть два подхода: индуктивное определение, которое вы дали, или абстрактный тип данных, использующий что-то другое для внутреннего представления.
Обратите внимание, что индуктивное представление не очень эффективно для больших чисел; однако это может быть лениво, что позволяет вам делать такие вещи, как видеть, какой из двух натс больше, не оценивая дальше, чем размер меньшего.
Абстрактный тип данных - это тип, который определен в отдельном модуле и не экспортирует его конструкторы, например IO
или Data.Set.Set
. Вы можете определить что-то вроде этого:
module Nat (Nat() {- etc. -} ) where
newtype Nat = Nat { unNat :: Integer }
... где вы экспортируете различные операции на Nat
, так что, хотя внутреннее представление равно Integer
, вы гарантируете, что никакое значение типа Nat
не будет создано с отрицательным значением значение.
В обоих случаях, если вы хотите использовать числовые литералы, вам понадобится определение fromInteger
, которое присоединено к классу типа Num
, что совершенно неверно для натуральных чисел, ну да ладно.
Если вы не против создать сломанный экземпляр только для того, чтобы получить синтаксические тонкости, вы можете сделать что-то вроде этого:
instance Num Nat where
Zero + n = n
n + Zero = n
(Succ n1) + (Succ n2) = Succ . Succ $ n1 + n2
fromInteger 0 = Zero
fromInteger i | i > 0 = Succ . fromInteger $ i - 1
... и т. Д. Для других функций. То же самое можно сделать для подхода с абстрактным типом данных, просто следите за тем, чтобы не использовал deriving
для получения автоматического Num
экземпляра , потому что он успешно нарушит ваше неотрицательное ограничение.