Сигнатура типа 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.