Может кто-нибудь сказать мне, почему 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
типы закрываются ниже **
. Поскольку нет Fractional
примера для Int
, вы не можете возвести его в отрицательную степень.
В идеале второй аргумент ^
должен быть статически неотрицательным (в настоящее время 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.
Int
и Integer
. Чтобы иметь возможность иметь эти три объявления экземпляра, разрешение экземпляра должно использовать обратное отслеживание, и ни один компилятор Haskell не реализует это.
Он не определяет два оператора - он определяет три! Из отчета:
Есть три операции возведения в степень с двумя аргументами: (
^
) увеличивает любое число до неотрицательной целой степени, (^^
) увеличивает дробное число до любой целой степени и (**
) принимает два аргумента с плавающей запятой. Значениеx^0
илиx^^0
равно 1 для любогоx
, включая ноль;0**y
не определено.
Это означает, что существует три разных алгоритма, два из которых дают точные результаты ( ^
и ^^
), а **
дают приблизительные результаты. Выбирая, какой оператор использовать, вы выбираете, какой алгоритм вызывать.
^
требует, чтобы его второй аргумент был Integral
. Если я не ошибаюсь, реализация может быть более эффективной, если вы знаете, что работаете с интегральной экспонентой. Кроме того, если вы хотите что-то вроде 2 ^ (1.234)
, даже если ваша база является целым, 2, ваш результат, очевидно, будет дробным. У вас есть больше возможностей, чтобы вы могли более жестко контролировать, какие типы входят и выходят из вашей функции возведения в степень.
Система типов Haskell не преследует тех же целей, что и другие системы типов, такие как C, Python или Lisp. Утиная печать - это (почти) противоположность мышления Haskell.
class Duck a where quack :: a -> Quack
определяет, что мы ожидаем от утки, а затем каждый экземпляр определяет что-то, что может вести себя как утка.
Duck
.