Почему я не могу добавить Integer в Double в Haskell? - программирование
Подтвердить что ты не робот

Почему я не могу добавить Integer в Double в Haskell?

Почему я могу это сделать:

1 + 2.0

но при попытке:

let a = 1
let b = 2.0
a + b

<interactive>:1:5:
    Couldn't match expected type `Integer' with actual type `Double'
    In the second argument of `(+)', namely `b'
    In the expression: a + b
    In an equation for `it': it = a + b

Это кажется просто странным! Вы когда-нибудь путешествуете?

P.S.: Я знаю, что "1" и "2.0" являются полиморфными константами. Меня это не беспокоит. Меня беспокоит то, почему haskell делает одно в первом случае, а другое во втором!

4b9b3361

Ответ 1

Вы можете использовать GHCI, чтобы узнать об этом немного больше. Используйте команду :t, чтобы получить тип выражения.

Prelude> :t 1
1 :: Num a => a

So 1 - константа, которая может быть любым числовым типом (Double, Integer и т.д.)

Prelude> let a = 1
Prelude> :t a
a :: Integer

Итак, в этом случае Haskell предположил, что конкретный тип для a равен Integer. Аналогично, если вы пишете let b = 2.0, то Haskell указывает тип Double. Используя let, сделанный Haskell вывел более конкретный тип, чем (возможно), был необходим, и это приводит к вашей проблеме. (Кто-то, у кого больше опыта, чем я, может, возможно, комментировать, почему это так). Поскольку (+) имеет тип Num a => a -> a -> a, два аргумента должны иметь один и тот же тип.

Вы можете исправить это с помощью функции fromIntegral:

Prelude> :t fromIntegral
fromIntegral :: (Num b, Integral a) => a -> b

Эта функция преобразует целочисленные типы в другие числовые типы. Например:

Prelude> let a = 1
Prelude> let b = 2.0
Prelude> (fromIntegral a) + b
3.0

Ответ 2

Типичная подпись (+) определяется как Num a => a -> a -> a, что означает, что она работает с любым членом класса Num, но оба аргумента должны быть одного типа.

Проблема здесь в том, что GHCI и порядок устанавливают типы, а не сам Haskell. Если бы вы поставили один из ваших примеров в файл (используя do для выражений let), он будет компилироваться и работать нормально, потому что GHC будет использовать всю функцию в качестве контекста для определения типов литералов 1 и 2.0.

Все, что происходит в первом случае, - это GHCI, который догадывается о типах номеров, которые вы вводите. Наиболее точным является Double, поэтому он просто предполагает, что другой должен был быть Double и выполняет вычисление. Однако, когда вы используете выражение let, у него есть только один номер для работы, поэтому он решает, что 1 является Integer и 2.0 является Double.

EDIT: GHCI на самом деле не "угадывает", он использует очень специфичные правила по умолчанию, определенные в отчете Haskell. Вы можете прочитать немного об этом здесь.

Ответ 3

Первый работает, потому что числовые литералы являются полиморфными (они интерпретируются как fromInteger literal и fromRational literal), поэтому в 1 + 2.0 у вас действительно есть fromInteger 1 + fromRational 2, при отсутствии других ограничений тип результата по умолчанию до Double.

Второе не работает из-за ограничения мономорфизма. Если вы связываете что-то без сигнатуры типа и с простой привязкой шаблона (name = expresion), этому объекту присваивается мономорфный тип. Для литерала 1 у нас есть ограничение Num, поэтому в соответствии с правилами по умолчанию его тип по умолчанию равен Integer в привязке let a = 1. Аналогично, тип дробного литерала по умолчанию равен Double.

Это будет работать, кстати, если вы :set -XNoMonomorphismRestriction в ghci.

Причина ограничения мономорфизма заключается в том, чтобы предотвратить потерю совместного использования, если вы видите значение, которое выглядит как константа, вы не ожидаете, что оно будет рассчитано более одного раза, но если оно имеет полиморфный тип, перепрограммироваться каждый раз, когда он используется.

Ответ 4

Другие очень хорошо рассмотрели многие аспекты этого вопроса. Я хотел бы сказать несколько слов о причине, почему + имеет подпись типа Num a => a -> a -> a.

Во-первых, класс Num не имеет возможности преобразовать один произвольный экземпляр Num в другой. Предположим, у меня есть тип данных для мнимых чисел; они все еще номера, но вы действительно не можете правильно преобразовать их в просто Int.

Во-вторых, какую подпись типа вы предпочитаете?

(+) :: (Num a, Num b) => a -> b -> a
(+) :: (Num a, Num b) => a -> b -> b
(+) :: (Num a, Num b, Num c) => a -> b -> c

После рассмотрения других вариантов вы понимаете, что a -> a -> a - самый простой выбор. Полиморфные результаты (как в третьем предложении выше) являются прохладными, но иногда могут быть слишком универсальными для удобного использования.

В-третьих, Haskell не Blub. Большинство, хотя, возможно, не все, дизайнерские решения о Haskell не учитывают условностей и ожиданий популярных языков. Мне часто нравится говорить, что первый шаг к обучению Haskell - сначала отучить все, что вы думаете о программировании. Я уверен, что большинство, если не все, опытных Haskellers, были сбиты с ног типом класса и различными другими курьезами Haskell, потому что большинство из них сначала выучили более "основной" язык. Но будьте терпеливы, вы в конечном итоге достигнете нирваны Хаскелла.:)