Может ли кто-нибудь сказать мне, почему Haskell Prelude определяет две отдельные функции для возведения в степень (т.е. ^
и **
)? Я думал, что система типов должна была устранить этот тип дублирования.
Prelude> 2^2
4
Prelude> 4**0.5
2.0
Может ли кто-нибудь сказать мне, почему Haskell Prelude определяет две отдельные функции для возведения в степень (т.е. ^
и **
)? Я думал, что система типов должна была устранить этот тип дублирования.
Prelude> 2^2
4
Prelude> 4**0.5
2.0
На самом деле существуют три оператора возведения в степень: (^)
, (^^)
и (**)
. ^
- неотрицательное интегральное возведение в степень, ^^
- целочисленное возведение в степень, а **
- возведение в степень с плавающей запятой:
(^) :: (Num a, Integral b) => a -> b -> a
(^^) :: (Fractional a, Integral b) => a -> b -> a
(**) :: Floating a => a -> a -> a
Причиной является безопасность типа: результаты числовых операций обычно имеют тот же тип, что и входные аргументы. Но вы не можете поднять Int
до уровня с плавающей запятой и получить результат типа Int
. И поэтому система типов не позволяет вам этого сделать: (1::Int) ** 0.5
создает ошибку типа. То же самое касается (1::Int) ^^ (-1)
.
Другой способ: Num
типы закрываются в ^
(они не обязательно должны иметь мультипликативный обратный), Fractional
типы закрываются в ^^
, Floating
типы закрываются в **
. Поскольку для Int
нет экземпляра Fractional
, вы не можете поднять его до отрицательной мощности.
В идеале второй аргумент ^
будет статически ограничен неотрицательным (в настоящее время 1 ^ (-2)
генерирует исключение во время выполнения). Но нет никакого типа для натуральных чисел в Prelude
.
Система типа Haskell недостаточно сильна, чтобы выразить три оператора возведения в степень как один. То, что вы действительно хотите, это что-то вроде этого:
class Exp a b where (^) :: a -> b -> a
instance (Num a, Integral b) => Exp a b where ... -- current ^
instance (Fractional a, Integral b) => Exp a b where ... -- current ^^
instance (Floating a, Floating b) => Exp a b where ... -- current **
Это действительно не работает, даже если вы включите расширение класса с несколькими параметрами, потому что выбор экземпляра должен быть более умным, чем позволяет Haskell.
Он не определяет два оператора - он определяет три! Из отчета:
Существует три операции экспоненции с двумя аргументами: (
^
) поднимает любое число до неотрицательной целочисленной мощности, (^^
) поднимает дробное число до любой целочисленной мощности, а (**
) принимает два плавающих числа, точечные аргументы. Значениеx^0
илиx^^0
равно 1 для любогоx
, включая ноль;0**y
- undefined.
Это означает, что существуют три разных алгоритма, два из которых дают точные результаты (^
и ^^
), а **
дает приблизительные результаты. Выбирая, какой оператор использовать, вы выбираете, какой алгоритм вызывается.
^
требует, чтобы его второй аргумент был Integral
. Если я не ошибаюсь, реализация может быть более эффективной, если вы знаете, что работаете с интегральным показателем. Кроме того, если вы хотите что-то вроде 2 ^ (1.234)
, хотя ваша база является интегральной, 2, ваш результат, очевидно, будет дробным. У вас есть больше возможностей, чтобы вы могли более жестко контролировать, какие типы входят в вашу функцию экспоненции и из нее.
Система типа Haskell не имеет той же цели, что и другие системы типов, такие как C, Python или Lisp. Утиная типизация (почти) противоположна мышлению Хаскелла.