Хтось може сказати мені, чому Прелюдія Haskell визначає дві окремі функції для піднесення до степені (тобто ^
і **
)? Я думав, що система типу повинна була усунути подібне дублювання.
Prelude> 2^2
4
Prelude> 4**0.5
2.0
Хтось може сказати мені, чому Прелюдія Haskell визначає дві окремі функції для піднесення до степені (тобто ^
і **
)? Я думав, що система типу повинна була усунути подібне дублювання.
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
.
Система типу Хаскелла недостатньо потужна, щоб виразити три оператори степенізації як один. Що б ви насправді хотіли, це приблизно так:
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, ваш результат, очевидно, буде дробовим. У вас є більше варіантів, щоб ви могли більш чітко контролювати, які типи входять і виходять з вашої функції піднесення до степені.
Система типу Хаскелла не має тієї ж мети, що й інші системи типів, такі як C, Python або Lisp. Введення качок (майже) протилежне мисленню Хаскелла.
class Duck a where quack :: a -> Quack
визначає, що ми очікуємо від качки, а потім кожен екземпляр визначає щось, що може поводитися як качка.
Duck
.