Сигнатура типа haskell для целых чисел
Скажем, я хочу написать функцию, чтобы определить, является ли заданное целочисленное число простым, какую подпись типа использовать?
isPrime :: Int -> Bool
или
isPrime :: (Integral a) => a -> Bool
Какая разница? Есть ли какая-то особая причина выбора одного над другим?
Если да, то в каких ситуациях я должен использовать два соответственно?
Ответы
Ответ 1
Тип Int -> Bool
означает, что ваша функция работает со значениями типа Int
, которые представляют собой ограниченные по размеру целые числа (максимальный размер, я полагаю, зависит от машины).
Тип (Integral a) => a -> Bool
означает, что ваша функция работает с значениями любого типа, у которого есть экземпляр типа Integral
type - i.e., типы, которые ведут себя как целые числа определенным образом. Основной причиной выбора этого по конкретному типу является создание более универсальной функции.
Общие формы с использованием Integral
имеют тенденцию быть наиболее полезными, когда вам нужно работать с целыми типами в других контекстах - хорошим примером является место, где стандартная библиотека не может этого сделать, например. функции, такие как replicate :: Int -> a -> [a]
. Код, который работает на каком-то конкретном целочисленном типе в своих целях, который хочет использовать этот тип с replicate
, поэтому должен сначала преобразовать в Int
или импортировать genericReplicate
из Data.List
.
В вашем случае вы можете рассмотреть тип Integer
, представляющий целые числа произвольного размера. Поскольку ваша основная цель - вычисление, меньше значения для поддержки произвольных интегральных типов.
Если мне нужна память, единственными экземплярами Integral
в стандартной библиотеке являются Int
и Integer
. ( ИЗМЕНИТЬ. Как напоминает мне hammar в комментариях, в типах Data.Int
и Data.Word
имеются экземпляры с фиксированным размером. Существуют также такие типы, как CInt
, но я не обращал внимания на те преднамеренно.)
Ответ 2
Я бы порекомендовал подпись
isPrime :: Integer -> Bool
Подпись isPrime :: Int -> Bool
исключала бы быстрые тесты для довольно больших чисел, поскольку эти тесты часто переполнялись тогда (технически это также относится к Integer
, по крайней мере, в версии, предоставляемой integer-gmp
, но вы, скорее всего, быть вне памяти задолго до того, как это имеет значение, поэтому мы можем поддерживать фикцию бесконечного типа Integer
).
Тип isPrime :: Integral a => a -> Bool
будет ложью с любой возможной реализацией. Можно было бы иметь экземпляр Integral
для моделирования типов Z[sqrt(2)]
(хотя для такого типа toInteger
было бы неверным), для такого типа 2 не будет простым, как бы определить это с помощью общего тест?
Или рассмотрим конечные типы, моделирующие факторкольцо Z/(n)
. Экземпляр Ord
для таких типов был бы несовместим с арифметикой, но мы уже имеем это для Int
и т.д. Например, в Z(6) = {0,1,2,3,4,5}
primes будет 2, 3 и 4 (обратите внимание, что ни один из них не является irreducible, 2 = 4 * 2, 3 = 3 * 3, 4 = 2 * 2).
Таким образом, единственным значимым и выполнимым тестом является "рациональное (или естественное) число, значение которого находится в диапазоне типа?". Это фиксируется (насколько это возможно, не жертвуя слишком большой скоростью) в типе isPrime :: Integer -> Bool
, при необходимости сочетаясь с toInteger
.
Ответ 3
Многие тесты прочности являются вероятностными и нуждаются в случайных числах, поэтому, по крайней мере, на самом низком уровне у них будет такая сигнатура типа:
seemsPrime :: (Integral a) => a -> [a] -> Bool
Интегральное ограничение кажется разумным, потому что обычно вам не нужен конкретный тип, а только операции типа rem
.