Ответ 1
Когда вы пишете fibsA :: Num a => [a]
, компилятор строит то, что по существу
fibsA :: NumDict a -> [a]
Где
data NumDict a = NumDict
{ (+) :: a -> a -> a
, (-) :: a -> a -> a
, (*) :: a -> a -> a
, negate :: a -> a
, abs :: a -> a
, signum :: a -> a
, fromInteger :: Integer -> a
}
Обратите внимание, что Num a
переместился из ограничения на аргумент функции. Класс типов - это просто таблица поиска для каждого типа, реализующего класс. Итак, для Num
, по умолчанию вы бы указали
mkInteger_NumDict :: NumDict Integer
mkInteger_NumDict = NumDict
{ (+) = integer_plus
, (-) = integer_minus
, (*) = integer_mult
, ...
}
mkInt_NumDict :: NumDict Int
mkFloat_NumDict :: NumDict Float
mkDouble_NumDict :: NumDict Double
и они автоматически передаются функции с использованием класса типов при разрешении экземпляра. Это означает, что наша функция fibsA
по существу принимает аргумент. Когда вы вызываете его из GHCi, правила по умолчанию нажимают и выбирают Integer
, но поскольку он называется таким образом, он будет выглядеть больше как внутри:
{-# RecordWildCards #-} -- To reduce typing
fibsA :: NumDict a -> [a]
fibsA [email protected](NumDict{..}) = fromInteger 0 : fromInteger 1 : zipWith (+) (fibsA nd) (tail $ fibsA nd)
Вы видите проблему с этим? Он по-прежнему рекурсивный, но теперь он должен сделать вызов функции на каждом шаге, снижая производительность. Если вы хотите сделать это очень быстро, умный программист сделает
fibsA [email protected](NumDict{..}) = fromInteger 0 : fromInteger 1 : zipWith (+) fibsA' (tail fibsA')
where fibsA' = fibsA nd
Это, по крайней мере, позволяет memoization. Однако двоичный файл haskell не может выполнять эту оптимизацию во время выполнения, что происходит во время компиляции. Итак, в конечном итоге вы получаете более медленную рекурсивную функцию. С fibsB
вы указываете тип конкретно, нет никаких полиморфных ограничений на его подпись типа. Значение fibsB
не имеет неявных или явных аргументов, поэтому при упоминании его указатель на тот же объект в памяти. fibsA
- это указатель на функцию, поэтому при использовании рекурсивно он возвращает новые объекты в памяти и не имеет memoization. Вот почему fibsB
быстрее, чем fibsA
, оптимизируется только fibsB
, потому что компилятор не должен заставить его работать для всех Num
, только Integer
.