Есть ли практический способ использования натуральных чисел в 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.