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