Haskell: функция ограничения типа Double для работы только с целыми числами - PullRequest
4 голосов
/ 14 марта 2010

Предположим, я пишу функцию, которая принимает список целых чисел и возвращает только те целые числа в списке, которые меньше 5.2. Я мог бы сделать что-то вроде этого:

belowThreshold = filter (< 5.2)

Достаточно просто, верно? Но теперь я хочу ограничить эту функцию, чтобы она работала только со списками ввода типа [Int] по моим собственным причинам. Это похоже на разумную просьбу. Увы нет. Объявление, которое ограничивает типы следующим образом:

belowThreshold :: [Integer] -> [Integer]
belowThreshold = filter (< 5.2)

Вызывает ошибку типа. Так что же здесь за история? Почему выполнение фильтра (<5.2), по-видимому, преобразует мой входной список в двойные? Как я могу сделать версию этой функции, которая принимает только целочисленные списки и возвращает только целочисленные списки? Почему система типов ненавидит меня? </p>

Ответы [ 4 ]

8 голосов
/ 14 марта 2010

Проверьте выводимый тип ниже Threshold в ghci, прежде чем добавлять аннотацию:

> :t belowThreshold
belowThreshold :: [Double] -> [Double]

Звучит так, как вы ожидали Num a => [a] -> [a], когда вы сказали "ограничить эту функцию". Вы фактически меняете тип функции, когда добавляете аннотацию [Integer] -> [Integer].

Чтобы сделать это, используйте явное преобразование:

belowThreshold = filter ((< 5.2) . fromIntegral)

Теперь belowThreshold :: [Integer] -> [Integer] как ты и хотел. Но целые числа преобразуются в двойные, прежде чем сравнивать с 5.2.

Так зачем вам конвертация? Ошибка типа, вероятно, ввела вас в заблуждение: список целых чисел не был преобразован в двойные по сравнению с 5.2, реальная проблема заключается в том, что только Двойные числа можно сравнить с двойными, поэтому вы должны передать список двойников на belowThreshold. У Haskell нет неявных преобразований, даже между числами. Если вы хотите конверсии, вы должны написать их самостоятельно.

Я хочу ограничить эту функцию работой только со списками ввода типа [Int] по моим собственным причинам. Это похоже на разумный запрос.

Ну, с точки зрения системы типов, нет. Это разумный код?

'c' < "foo"

А как насчет этого?

12 < "bar"

Все эти значения являются экземплярами Ord, но вы не можете использовать их вместе с (<). У Haskell нет неявных преобразований. Поэтому, даже если два значения являются экземплярами Num и Ord, вы не сможете сравнить их с (<), если они относятся к разным типам.

3 голосов
/ 14 марта 2010

Вы пытаетесь сравнить целое число с двойным (5.2). Хаскеллу это не нравится. Попробуйте использовать

filter (< 6)
1 голос
/ 16 марта 2010

Синтаксис 5.2 действителен для любого Fractional. Int не является экземпляром Fractional, не может и не должно быть. Что делать при преобразовании произвольного Rational в Int не указано.

Преобразование в Double из произвольной дроби, однако, имеет вполне разумный смысл (в пределах диапазона типа).

Ваше ожидание определяется наличием неявных принуждений во многих языках.

Тем не менее, они идут с оплатой. Вы должны вручную убедиться, что вся система принуждений является слитой. Haskell не делает этого, выбирая вместо этого, чтобы позволить числовому литеральному синтаксису использовать систему типов. Для преобразования между ними вам нужно использовать fromIntegral, чтобы явно указать необходимость принуждения, это позволяет избежать зависимости от слияния и позволяет программистам определять новые числовые типы.

belowThreshold = filter (\x -> fromIntegral x < 5.2) 

Это аналогично использованию явного преобразования в C ++, например ((double)x < 5.2). Хотя этот оператор работает только из-за значения по умолчанию, потому что 5.2 может использоваться в качестве члена any Fractional, а результатом 'fromIntegral x' является любой Num, суперкласс Fractional, поэтому fromIntegral x < 5.2 не указан, он просто знает, что ему нужно сравнить два Fractional значения одного и того же типа, и он выбирает Double в качестве разумного значения по умолчанию, основываясь на выражении «default».

Также обратите внимание, что Int не является единственным типом Integral, поэтому вышеуказанный метод работает с любым списком значений Integral:

belowThreshold :: Integral a => [a] -> [a]
1 голос
/ 14 марта 2010

Если вы должны использовать двойное (скажем, это аргумент), я бы использовал ceiling:

filter (< (ceiling 5.2))

Теперь, если вы хотите, чтобы функция принимала ограничивающее значение как «любое» (релевантное) числовое значение, вы можете создать свой собственный класс типов, который будет соответствовать вашему числу.

class Ceilingable a where
  ceil :: (Integral b) => a -> b

instance (RealFrac a) => Ceilingable a where
  ceil = ceiling

instance (Integral a) => Ceilingable a where
  ceil = fromIntegral

belowThreshold :: (Ceilingable a) => a -> [Integer] -> [Integer]
belowThreshold threshold = filter (< ceil threshold)
...