Есть ли практический способ использования натуральных чисел в Haskell?

Я изучаю Haskell и хотел бы ввести использование положительных целых чисел (1,2,3,...) в некоторых конструкторах, но я, похоже, нахожу только типы данных " Int " и "Integer".

Я мог бы использовать канонические

data Nat = Zero | Succ Nat

но тогда я не мог использовать 1, 4, ... чтобы обозначить их.

поэтому я спрашиваю, есть ли способ сделать это? (это похоже на использование "unsigned" в C)

спасибо заранее.

EDIT: Я собираюсь спрятать его внутри модуля, как объяснил C. A. McCann. Кроме того, я должен добавить следующую ссылку:http://haskell.org/haskellwiki/Smart_constructors для резюме по этому вопросу. Спасибо, что нашли время ответить!

3 ответов


для этого обычно существует два подхода: индуктивное определение, которое вы дали, или абстрактный тип данных, использующий что-то еще для внутреннего представления.

обратите внимание, что индуктивное представление не очень эффективно для больших чисел; однако оно может быть ленивым, что позволяет вам делать такие вещи, как видеть, какой из двух nats больше, не оценивая дальше размера меньшего.

абстрактным типом данных является тот, который определен в отдельном модуль и не экспортирует свои конструкторы, примерами являются IO или Data.Set.Set. Вы можете определить что-то вроде этого:

module Nat (Nat() {- etc. -} ) where

newtype Nat = Nat { unNat :: Integer }

...где вы экспортируете различные операции на Nat такое, что, хотя внутреннее представление это просто Integer, вы гарантируете, что нет значения типа Nat строится с отрицательным значением.

в обоих случаях, если вы хотите использовать числовые литералы, вам потребуется определение fromInteger, который прилагается к the 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 экземпляр, потому что он с радостью сломает ваше неотрицательное ограничение.


вы можете использовать Word32 от данные.Слово, что соответствует uint32_t в с.

С Word32 вы получаете те же проблемы, что и с неподписанными типами в C, особенно over - и underflow. Если вы хотите убедиться, что этого не произойдет, вам нужно будет обернуть его в newtype и экспортировать только интеллектуальный конструктор. Таким образом, нет сложения, вычитания и т. д. можно было бы и нет никакого риска или потери значимости. Например, если вы хотите поддержать добавление, вы можете добавить и экспортировать функцию для добавления неподписанных целых чисел, но с проверкой на переполнение (и производительности). Тогда это могло бы выглядеть так:

module NT(UInt, addUInts) where

import Data.Word

newtype UInt = UInt Word32
  deriving (Show)

mkUInt :: Word32 -> UInt
mkUInt = UInt

addUInts :: UInt -> UInt -> Maybe UInt
addUInts (UInt u1) (UInt u2) =
  let u64 :: Word64
      u64 = fromIntegral u1 + fromIntegral u2
  in if u64 > fromIntegral (maxBound :: Word32)
       then Nothing
       else Just (UInt (fromIntegral u64))

Я не могу вспомнить, касается ли он вашего конкретного вопроса, но вам может понравиться статья Колина Рансимена Как насчет натуральных чисел?. В случае, если вы не можете получить через paywall, кажется, есть версия в Citeseer.