Основная проблема в том, что
let x = 1 + 2
определяет polymorphi c значение типа forall a. Num a => a
, и это то, что оценивается аналогично функции.
Каждое использование x
может быть выполнено в другом типе, например x :: Int
, x :: Integer
, x :: Double
и так далее. Эти результаты никоим образом не «кэшируются», а пересчитываются каждый раз, как если бы x
была функцией, которая, так сказать, вызывается несколько раз.
Действительно, общая реализация классов типов реализует такие полиморф c x
как функция
x :: NumDict a -> a
, где приведенный выше аргумент NumDict a
автоматически добавляется компилятором и содержит информацию о том, что a
является типом Num
, включая как выполнить сложение, как интерпретировать целочисленные литералы внутри a
и так далее. Это называется реализацией «передачи словаря».
Таким образом, использование polymorphi c x
несколько раз действительно соответствует вызову функции несколько раз, что вызывает повторное вычисление. Чтобы избежать этого, (* страшное) ограничение мономорфизма было введено в Haskell, заставив x
быть мономорфным c. MR не является идеальным решением и в некоторых случаях может привести к неожиданным ошибкам типа.
Чтобы решить эту проблему, MR отключен по умолчанию в GHCi, поскольку в GHCi нас не очень заботит производительность - удобство использования там важнее. Это, однако, вызывает повторное вычисление, как вы обнаружили.