Подтвердить что ты не робот

Экспоненциальность в Haskell

Может ли кто-нибудь сказать мне, почему Haskell Prelude определяет две отдельные функции для возведения в степень (т.е. ^ и **)? Я думал, что система типов должна была устранить этот тип дублирования.

Prelude> 2^2
4
Prelude> 4**0.5
2.0
4b9b3361

Ответ 1

На самом деле существуют три оператора возведения в степень: (^), (^^) и (**). ^ - неотрицательное интегральное возведение в степень, ^^ - целочисленное возведение в степень, а ** - возведение в степень с плавающей запятой:

(^) :: (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.

Ответ 2

Система типа 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.

Ответ 3

Он не определяет два оператора - он определяет три! Из отчета:

Существует три операции экспоненции с двумя аргументами: (^) поднимает любое число до неотрицательной целочисленной мощности, (^^) поднимает дробное число до любой целочисленной мощности, а (**) принимает два плавающих числа, точечные аргументы. Значение x^0 или x^^0 равно 1 для любого x, включая ноль; 0**y - undefined.

Это означает, что существуют три разных алгоритма, два из которых дают точные результаты (^ и ^^), а ** дает приблизительные результаты. Выбирая, какой оператор использовать, вы выбираете, какой алгоритм вызывается.

Ответ 4

^ требует, чтобы его второй аргумент был Integral. Если я не ошибаюсь, реализация может быть более эффективной, если вы знаете, что работаете с интегральным показателем. Кроме того, если вы хотите что-то вроде 2 ^ (1.234), хотя ваша база является интегральной, 2, ваш результат, очевидно, будет дробным. У вас есть больше возможностей, чтобы вы могли более жестко контролировать, какие типы входят в вашу функцию экспоненции и из нее.

Система типа Haskell не имеет той же цели, что и другие системы типов, такие как C, Python или Lisp. Утиная типизация (почти) противоположна мышлению Хаскелла.