Может кто-нибудь сказать мне, почему 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.