Посилення в Хаскелі


91

Хтось може сказати мені, чому Прелюдія Haskell визначає дві окремі функції для піднесення до степені (тобто ^і **)? Я думав, що система типу повинна була усунути подібне дублювання.

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

Система типу Хаскелла недостатньо потужна, щоб виразити три оператори степенізації як один. Що б ви насправді хотіли, це приблизно так:

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
Ви, звичайно, не можете написати його так, як я пропоную, але може бути якийсь спосіб кодування.
серпень

2
@ErikAllik, ймовірно, робить це для стандартного Haskell, оскільки жоден новий звіт Haskell не виходив з 2010 року.
Мартін Каподічі,

10

Він не визначає двох операторів - він визначає трьох! З доповіді:

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

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


4

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

Система типу Хаскелла не має тієї ж мети, що й інші системи типів, такі як C, Python або Lisp. Введення качок (майже) протилежне мисленню Хаскелла.


4
Я не цілком погоджуюсь з тим, що тип мислення Хаскела є протилежністю набору качок. Класи типу Haskell дуже схожі на набір качок. class Duck a where quack :: a -> Quackвизначає, що ми очікуємо від качки, а потім кожен екземпляр визначає щось, що може поводитися як качка.
серпень

9
@augustss Я бачу, звідки ти родом. Але неформальний девіз, що стоїть перед набором качок, полягає в тому, що "якщо це схоже на качку, діє як качка, а шарлатани схожі на качку, то це качка". У Haskell це не качка, якщо це не оголошено екземпляром Duck.
Ден Бертон,

1
Це правда, але саме цього я очікував би від Хаскелла. Ви можете зробити все, що завгодно, качку, але ви повинні бути чітко про це. Ми не хочемо помилитися з тим, чого ми не просили, щоб бути качкою.
серпень

Існує більш конкретна різниця між способом роботи Хаскела та набором качок. Так, ви можете дати будь-який тип класу Качка, але це не Качка. Звичайно, він здатний крякати, але все одно, конкретно, якого б типу він не був. Ви все ще не можете мати список качок. Функція, яка приймає список качок, а також змішування та відповідність різним типам класу качка не буде працювати. У цьому відношенні Хаскелл не дозволяє просто сказати: "Якщо воно шаркає, як качка, значить, це качка". У Haskell всі ваші качки повинні бути квакерами одного типу. Це зовсім інше, ніж справді набирання качок.
mmachenry

Ви можете мати список змішаних качок, але вам потрібно розширення екзистенціальної кількісної оцінки.
Болпат,
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.