Возведение в степень в Haskell

91

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

Prelude> 2^2
4
Prelude> 4**0.5
2.0
небесная птица
источник

Ответы:

130

Есть на самом деле три оператора возведения в степень: (^), (^^)и (**). ^является неотрицательным целым возведением в степень, ^^является целым возведением в степень и **является возведением в степень с плавающей запятой:

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

Михаил Глушенков
источник
31

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

август
источник
4
Верно ли утверждение о том, что это невозможно реализовать? IIRC, haskell имеет возможность, чтобы второй параметр класса многопараметрического типа определялся строго по первому параметру. Есть ли еще одна проблема, которая не может быть решена?
RussellStewart
2
@singular Это все еще правда. Первый аргумент не определяет второй, например, вы хотите, чтобы экспонента была равна Intи Integer. Чтобы иметь возможность иметь эти три объявления экземпляра, разрешение экземпляра должно использовать обратное отслеживание, и ни один компилятор Haskell не реализует это.
августа
7
Сохраняется ли аргумент «система типов недостаточно сильна» по состоянию на март 2015 года?
Эрик Каплун,
3
Вы, конечно, не можете написать это так, как я предлагаю, но может быть способ его закодировать.
августа 2015,
2
@ErikAllik, вероятно, подходит для стандартного Haskell, поскольку с 2010 года не выходил ни один новый отчет Haskell.
Мартин Каподичи,
10

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

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

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

Гейб
источник
4

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

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

Дэн Бертон
источник
4
Я не полностью согласен с тем, что тип мышления в Haskell противоположен утиной печати. Классы типов Haskell во многом похожи на утиную печать. class Duck a where quack :: a -> Quackопределяет, что мы ожидаем от утки, а затем каждый экземпляр определяет что-то, что может вести себя как утка.
август
9
@augustss Я вижу, откуда вы. Но неофициальный девиз, стоящий за утиным набором текста, звучит так: «Если он выглядит как утка, ведет себя как утка и крякает как утка, то это утка». В Haskell это не утка, если она не объявлена ​​экземпляром Duck.
Дэн Бертон
1
Это правда, но этого я ожидал от Haskell. Вы можете сделать все, что захотите, но вы должны четко заявить об этом. Мы не хотим ошибиться в том, о чем не просили быть уткой.
август
Есть более конкретное различие между способами работы Haskell и утиной типизацией. Да, вы можете присвоить классу «Утка» любому типу, но это не «Утка». Конечно, он способен крякать, но все же конкретно, каким бы типом он ни был. У вас все еще нет списка уток. Функция, которая принимает список Ducks и смешивает и сопоставляет различные типы класса Duck, работать не будет. В этом отношении Haskell не позволяет вам просто сказать: «Если он крякает, как утка, значит, это утка». В Haskell все ваши утки должны быть шарлатанами одного типа. Это действительно сильно отличается от утиного набора текста.
mmachenry 01
У вас может быть список смешанных уток, но вам понадобится расширение экзистенциальной количественной оценки.
Bolpat