Як я використовую виправлення та як це працює?


87

Мене трохи розгубила документація fix(хоча, я думаю, я розумію, що вона повинна робити зараз), тому я подивився вихідний код. Це мене ще більше розгубило:

fix :: (a -> a) -> a
fix f = let x = f x in x

Як саме це повертає фіксовану точку?

Я вирішив спробувати це в командному рядку:

Prelude Data.Function> fix id
...

І він там висить. Тепер, щоб бути справедливим, це на моєму старому macbook, який є дещо повільним. Однак ця функція не може бути занадто обчислювально обчислювальною, оскільки все, що передається в id, повертає ту саму річ назад (не кажучи вже про те, що вона не з'їдає час процесора). Що я роблю не так?


68
Відповідь на витівку: "виправлення не має реального використання, воно просто є, щоб ви могли вводити fix errorghci і відчувати себе добре".
Thomas M. DuBuisson

3
@TomMD - смішно. Я пам'ятатиму, що якщо хтось коли-небудь запитає мене, що робить виправлення, і я відчуваю, що це не так. :-)
Джейсон Бейкер,

2
Я зазвичай пишу fixяк fix f = f (fix f). Короткий, простий, працює і ідентичний математичному визначенню.
newacct

20
@newacct, так, я теж про це думаю. Але той, що тут, може призвести до більш ефективних структур. Ви можете побачити різницю, якщо оціните, скажімо,fix (1:) !! (10^8) . Оригінал робить це в постійній пам’яті, у вас - лінійна пам’ять (що також робить її дещо повільнішою). Тобто використання функції let дозволяє «затягнути вузол» і дозволяє генерувати кругову структуру даних, тоді як ваша - ні.
luqui

22
Ви могли б і заново винайти fix! допоміг мені fixбагато чого зрозуміти .
переплив

Відповіді:


90

Ви нічого поганого не робите. fix id- нескінченний цикл.

Коли ми говоримо, що fixповертає найменш фіксовану точку функції, ми маємо на увазі, що в сенсі теорії області . Томуfix (\x -> 2*x-1) що це НЕ збирається повертатися 1, тому що , хоча 1фіксована точка цієї функції, вона не хоча б один в упорядкуванні домену.

Я не можу описати впорядкування доменів лише за два абзаци, тому я перенаправлю вас до посилання на теорію доменів вище. Це чудовий підручник, легкий для читання та досить просвітницький. Дуже рекомендую.

Для вигляду з 10 000 футів fix- це функція вищого порядку, яка кодує ідею рекурсії . Якщо у вас є вираз:

let x = 1:x in x

Що призводить до нескінченного списку [1,1..], ви можете сказати те саме, використовуючи fix:

fix (\x -> 1:x)

(Або просто fix (1:)), який говорить, знайдіть мені фіксовану точку (1:)функції, ВДАЙТЕ таке значення x, що x = 1:x... так само, як ми визначили вище. Як видно з визначення, fixє не що інше, як ця ідея - рекурсія, інкапсульована у функцію.

Це також справді загальне поняття рекурсії - ви можете записати будь-яку рекурсивну функцію таким чином, включаючи функції, які використовують поліморфну ​​рекурсію . Так, наприклад, типова функція Фібоначчі:

fib n = if n < 2 then n else fib (n-1) + fib (n-2)

Можна написати за допомогою fix таким чином:

fib = fix (\f -> \n -> if n < 2 then n else f (n-1) + f (n-2))

Вправа: розширте визначення, fixщоб показати, що ці два визначення fibеквівалентні.

Але для повного розуміння прочитайте про теорію доменів. Це справді круті речі.


32
Ось пов’язаний спосіб думати fix id: fixприймає функцію type a -> aі повертає значення type a. Оскільки idполіморфний для будь-якого a, fix idматиме тип a, тобто будь-яке можливе значення. У Haskell єдиним значенням, яке може бути будь-якого типу, є bottom, ⊥, і його неможливо відрізнити від обчислення, що не закінчується. Отже, fix idвиробляється саме те, що слід, нижня вартість. Небезпека fixполягає в тому, що якщо ⊥ є фіксованою точкою вашої функції, то вона за визначенням є найменш фіксованою точкою, тому fixне припиняється.
John L

4
@JohnL у Haskell undefined- це також значення будь-якого типу. Ви можете визначити , fixяк: fix f = foldr (\_ -> f) undefined (repeat undefined).
найпотужніший

1
@Diego ваш код еквівалентний _Y f = f (_Y f).
Will Ness

25

Я не претендую на те, щоб це взагалі розуміти, але якщо це комусь допомагає ... тоді їппі.

Розглянемо визначення fix. fix f = let x = f x in x. Приголомшлива частина - це те, що xвизначається як f x. Але подумайте про це хвилинку.

x = f x

Оскільки x = fx, то ми можемо підставити значення xправоруч від цього, так? Тож ...

x = f . f $ x -- or x = f (f x)
x = f . f . f $ x -- or x = f (f (f x))
x = f . f . f . f . f . f . f . f . f . f . f $ x -- etc.

Отже, фокус полягає в тому, що для того, щоб припинити, fпотрібно створити якусь структуру, щоб пізнішеf шаблон міг збігатися з цією структурою і припиняти рекурсію, насправді не дбаючи про повне «значення» її параметра (?)

Якщо, звичайно, ви не захочете зробити щось на зразок створення нескінченного списку, як це ілюструє luqui.

Пояснення факторіалів TomMD добре. Тип підпису FIX є (a -> a) -> a. Іншими словами, підпис типу - (\recurse d -> if d > 0 then d * (recurse (d-1)) else 1)це . Тож ми можемо це сказати . Таким чином, fix приймає нашу функцію, яка є , або насправді, і повертає результат типу , іншими словами, іншими словами, іншої функції!(b -> b) -> b -> b(b -> b) -> (b -> b)a = (b -> b)a -> a(b -> b) -> (b -> b)ab -> b

Зачекайте, я думав, це повинно було повернути фіксовану точку ... а не функцію. Ну, це так, начебто (оскільки функції - це дані). Ви можете собі уявити, що це дало нам остаточну функцію для пошуку факторіалу. Ми дали йому функцію, яка не знає, як повторити (отже, одним із параметрів для неї є функція, яка використовується для рекурсії), і fixнавчили її, як повторити.

Пам'ятаєте, як я сказав, що це fмає генерувати якусь структуру, щоб пізніший fшаблон міг збігатися і закінчуватися? Ну це не зовсім правильно, я здогадуюсь. TomMD проілюстрував, як ми можемо розширити, xщоб застосувати функцію і зробити крок до базового випадку. Для своєї функції він використовував if / then, і саме це спричиняє припинення. Після багаторазових замін inчастина цілого визначення fixврешті-решт перестає визначатися з точки зору, xі саме тоді воно є обчислювальним та повним.


Дякую. Це дуже корисне та практичне пояснення.
kizzx2

17

Вам потрібен спосіб припинення точки фіксування. Розширюючи приклад, очевидно, що він не закінчиться:

fix id
--> let x = id x in x
--> id x
--> id (id x)
--> id (id (id x))
--> ...

Ось реальний приклад того, як я використовую виправлення (зауважте, я не часто використовую виправлення і, напевно, втомився / не хвилювався щодо читабельного коду, коли я писав це):

(fix (\f h -> if (pred h) then f (mutate h) else h)) q

WTF, скажете ви! Ну так, але тут є кілька справді корисних моментів. Перш за все, ваш перший fixаргумент, як правило, повинен бути функцією, яка є "рецидивом", а другий аргумент - це дані, на основі яких слід діяти. Ось той самий код, що і названа функція:

getQ h
      | pred h = getQ (mutate h)
      | otherwise = h

Якщо ви все ще розгублені, можливо, факторіал буде простішим прикладом:

fix (\recurse d -> if d > 0 then d * (recurse (d-1)) else 1) 5 -->* 120

Зверніть увагу на оцінку:

fix (\recurse d -> if d > 0 then d * (recurse (d-1)) else 1) 3 -->
let x = (\recurse d -> if d > 0 then d * (recurse (d-1)) else 1) x in x 3 -->
let x = ... in (\recurse d -> if d > 0 then d * (recurse (d-1)) else 1) x 3 -->
let x = ... in (\d -> if d > 0 then d * (x (d-1)) else 1) 3

О, ти щойно це бачив? Це xстало функцією всередині нашої thenгілки.

let x = ... in if 3 > 0 then 3 * (x (3 - 1)) else 1) -->
let x = ... in 3 * x 2 -->
let x = ... in 3 * (\recurse d -> if d > 0 then d * (recurse (d-1)) else 1) x 2 -->

У наведеному вище вам потрібно пам’ятати x = f x, звідси два аргументи x 2в кінці, а не просто 2.

let x = ... in 3 * (\d -> if d > 0 then d * (x (d-1)) else 1) 2 -->

І я зупинюсь тут!


Ваша відповідь - це те, що насправді мало fixсенс для мене. Моя відповідь багато в чому залежить від того, що ви вже сказали.
Dan Burton

@ Томас обидві послідовності скорочення неправильні. :) id xпросто зменшується до x(що потім зменшується назад до id x). - Потім, у 2-му зразку ( fact), при xпершому примусовому натисканні отримане значення запам'ятовується і використовується повторно. Перерахунок (\recurse ...) xбуде відбуватися з без спільного визначення y g = g (y g), а ні з цим спільнимfix визначенням. - Я зробив пробну редакцію тут - ви можете її використовувати, або я можу внести правку, якщо ви схвалите.
Will Ness

насправді, коли fix idзменшується, let x = id x in xтакож змушує значення програми id xвсередині letкадру (thunk), тому воно зменшується до let x = x in x, і це цикли. Схоже.
Will Ness

Правильно. Моя відповідь - використання рівномірних міркувань. Показ зменшення а-ля Хаскелл, яке стосується порядку оцінки, лише заплутує приклад без жодної справжньої вигоди.
Thomas M. DuBuisson,

1
Питання позначене як haskell, так і letrec (тобто рекурсивний let, з обміном). Різниця між fixі Y дуже чітка і важлива в Хаскеллі. Я не бачу, що корисного подається, показуючи неправильний порядок зменшення, коли правильний ще коротший, набагато чіткіший і простіший для дотримання, і правильно відображає те, що насправді відбувається.
Will Ness

3

Наскільки я розумію, він знаходить значення для функції, яке видає те саме, що і ви. Захоплення полягає в тому, що він завжди вибере невизначений (або нескінченний цикл, у haskell, невизначені та нескінченні цикли однакові) або те, що містить найбільше невизначених. Наприклад, з ідентифікатором,

λ <*Main Data.Function>: id undefined
*** Exception: Prelude.undefined

Як бачите, undefined - це фіксована точка, тому fixвиберемо її. Якщо ви замість цього робите (\ x-> 1: x).

λ <*Main Data.Function>: undefined
*** Exception: Prelude.undefined
λ <*Main Data.Function>: (\x->1:x) undefined
[1*** Exception: Prelude.undefined

Тож fixне можна вибрати невизначений. Щоб зробити його трохи більш приєднаним до нескінченних петель.

λ <*Main Data.Function>: let y=y in y
^CInterrupted.
λ <*Main Data.Function>: (\x->1:x) (let y=y in y)
[1^CInterrupted.

Знову ж невелика різниця. Отже, що таке фіксована точка? Давайте спробуємо repeat 1.

λ <*Main Data.Function>: repeat 1
[1,1,1,1,1,1, and so on
λ <*Main Data.Function>: (\x->1:x) $ repeat 1
[1,1,1,1,1,1, and so on

Це теж саме! Оскільки це єдина фіксована точка, fixтреба зупинитися на ній. На жаль fix, для вас немає нескінченних циклів або невизначених.

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