Làm cách nào để sử dụng bản sửa lỗi và nó hoạt động như thế nào?


87

Tôi hơi bối rối trước tài liệu cho fix(mặc dù tôi nghĩ tôi hiểu nó phải làm gì bây giờ), vì vậy tôi đã xem mã nguồn. Điều đó khiến tôi bối rối hơn:

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

Làm thế nào chính xác thì điều này trả về một điểm cố định?

Tôi đã quyết định dùng thử tại dòng lệnh:

Prelude Data.Function> fix id
...

Và nó bị treo ở đó. Công bằng mà nói, đây là chiếc macbook cũ của tôi, hơi chậm. Tuy nhiên, chức năng này không thể quá tốn kém về mặt tính toán vì bất kỳ thứ gì được chuyển vào id đều trả lại điều tương tự (chưa kể đến việc nó không tốn thời gian của CPU). Tôi đang làm gì sai?


68
Câu trả lời chơi khăm là "sửa chữa không có công dụng thực sự, nó chỉ ở đó để bạn có thể gõ fix errorghci và cảm thấy hài lòng về bản thân."
Thomas M. DuBuisson

3
@TomMD - Buồn cười. Tôi sẽ nhớ điều đó nếu có ai hỏi tôi sửa chữa gì và tôi cảm thấy như thế nào. :-)
Jason Baker

2
Tôi thường viết fixnhư fix f = f (fix f). Ngắn gọn, đơn giản, hoạt động và giống với định nghĩa toán học.
newacct

20
@newacct, vâng, đó là cách tôi nghĩ về nó. Nhưng cái ở đây có thể dẫn đến cấu trúc hiệu quả hơn. Bạn có thể thấy sự khác biệt nếu bạn đánh giá, nói fix (1:) !! (10^8),. Bản gốc thực hiện điều đó trong bộ nhớ không đổi, bản gốc của bạn lấy bộ nhớ tuyến tính (khiến nó cũng chậm hơn một chút). Đó là, sử dụng phép để "thắt chặt một nút thắt hơn" và cho phép tạo cấu trúc dữ liệu vòng tròn, trong khi của bạn thì không.
luqui

22
Bạn cũng có thể phát minh fixlại! đã giúp tôi hiểu fixrất nhiều.
fredoverflow

Câu trả lời:


90

Bạn không làm gì sai. fix idlà một vòng lặp vô hạn.

Khi chúng tôi nói rằng fixtrả về điểm cố định nhỏ nhất của một hàm, chúng tôi muốn nói rằng theo nghĩa lý thuyết miền . Vì vậy, fix (\x -> 2*x-1)sẽ không trở lại 1, bởi vì mặc dù 1là một điểm cố định của hàm đó, nó không phải là điểm ít nhất trong thứ tự miền.

Tôi không thể mô tả thứ tự tên miền trong một hoặc hai đoạn văn, vì vậy tôi sẽ giới thiệu cho bạn liên kết lý thuyết tên miền ở trên. Đây là một hướng dẫn tuyệt vời, dễ đọc và khá thú vị. Tôi khuyên bạn nên nó.

Đối với chế độ xem từ 10.000 feet, fixlà một hàm bậc cao mã hóa ý tưởng của phép đệ quy . Nếu bạn có biểu thức:

let x = 1:x in x

Kết quả là trong danh sách vô hạn [1,1..], bạn có thể nói điều tương tự bằng cách sử dụng fix:

fix (\x -> 1:x)

(Hoặc đơn giản hơn fix (1:)), cho biết tìm cho tôi một điểm cố định của (1:)hàm, IOW một giá trị xsao cho x = 1:x... giống như chúng ta đã xác định ở trên. Như bạn có thể thấy từ định nghĩa, fixkhông có gì khác ngoài ý tưởng này - đệ quy được đóng gói thành một hàm.

Đó cũng là một khái niệm thực sự chung về đệ quy - bạn có thể viết bất kỳ hàm đệ quy nào theo cách này, kể cả các hàm sử dụng đệ quy đa hình . Ví dụ như hàm fibonacci điển hình:

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

Có thể được viết bằng cách fixnày:

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

Bài tập: mở rộng định nghĩa của fixđể chứng tỏ rằng hai định nghĩa fibnày là tương đương.

Nhưng để hiểu đầy đủ, hãy đọc về lý thuyết miền. Nó thực sự thú vị.


32
Đây là một cách liên quan để suy nghĩ về fix id: fixnhận một hàm của kiểu a -> avà trả về một giá trị của kiểu a. Bởi vì idlà đa hình cho bất kỳ a, fix idsẽ có kiểu a, tức là bất kỳ giá trị nào có thể. Trong Haskell, giá trị duy nhất có thể là bất kỳ kiểu nào là dưới cùng, ⊥, và không thể phân biệt được với một phép tính không kết thúc. Vì vậy, fix idsản xuất chính xác những gì nó nên, giá trị dưới cùng. Một điều nguy hiểm fixlà nếu ⊥ là một điểm cố định trong hàm của bạn, thì theo định nghĩa, nó là điểm cố định ít nhất , do đó fixsẽ không kết thúc.
John L

4
@JohnL trong Haskell undefinedcũng là một giá trị thuộc bất kỳ loại nào. Bạn có thể định nghĩa fixnhư sau: fix f = foldr (\_ -> f) undefined (repeat undefined).
didest

1
@Diego mã của bạn tương đương với _Y f = f (_Y f).
Will Ness

25

Tôi không hiểu điều này chút nào, nhưng nếu điều này giúp ích cho bất kỳ ai ... thì bạn nhé.

Xem xét định nghĩa của fix. fix f = let x = f x in x. Phần rối loạn tâm trí xđược định nghĩa là f x. Nhưng hãy nghĩ về nó trong một phút.

x = f x

Vì x = fx, nên chúng ta có thể thay thế giá trị của xở bên phải của nó, phải không? Vì vậy, do đó ...

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.

Vì vậy, mẹo là, để kết thúc, fphải tạo ra một số loại cấu trúc, để fmẫu sau này có thể khớp với cấu trúc đó và kết thúc đệ quy, mà không thực sự quan tâm đến "giá trị" đầy đủ của tham số của nó (?)

Tất nhiên, trừ khi bạn muốn làm điều gì đó như tạo một danh sách vô hạn, như luqui đã minh họa.

Giải thích giai thừa của TomMD là tốt. Chữ ký loại của Fix là (a -> a) -> a. Nói cách khác, chữ ký kiểu cho (\recurse d -> if d > 0 then d * (recurse (d-1)) else 1)là . Vì vậy, chúng tôi có thể nói rằng . Bằng cách đó, sửa chữa lấy hàm của chúng ta, tức là , hoặc thực sự, và sẽ trả về một kết quả kiểu , nói cách khác, nói cách khác, là một hàm khác!(b -> b) -> b -> b(b -> b) -> (b -> b)a = (b -> b)a -> a(b -> b) -> (b -> b)ab -> b

Chờ đã, tôi nghĩ nó phải trả về một điểm cố định ... không phải là một hàm. Đúng là như vậy, đại loại là (vì các hàm là dữ liệu). Bạn có thể tưởng tượng rằng nó cung cấp cho chúng ta hàm cuối cùng để tìm giai thừa. Chúng tôi đã cung cấp cho nó một hàm không biết cách đệ quy (do đó một trong các tham số của nó là một hàm được sử dụng để đệ quy) và fixdạy nó cách đệ quy.

Hãy nhớ cách tôi đã nói rằng fphải tạo ra một số loại cấu trúc để fmẫu sau này có thể khớp và kết thúc? Vâng, điều đó không chính xác, tôi đoán vậy. TomMD đã minh họa cách chúng ta có thể mở rộng xđể áp dụng hàm và từng bước hướng tới trường hợp cơ sở. Đối với chức năng của mình, anh ta đã sử dụng if / then, và đó là nguyên nhân gây ra sự kết thúc. Sau nhiều lần thay thế, inphần của toàn bộ định nghĩa fixcuối cùng không còn được định nghĩa về mặt xvà đó là khi nó có thể tính toán và hoàn chỉnh.


Cảm ơn. Đây là một lời giải thích rất hữu ích và thực tế.
kizzx2

17

Bạn cần một cách để điểm sửa lỗi kết thúc. Mở rộng ví dụ của bạn, rõ ràng là nó sẽ không kết thúc:

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

Đây là một ví dụ thực tế về việc tôi sử dụng bản sửa lỗi (lưu ý rằng tôi không sử dụng bản sửa lỗi thường xuyên và có lẽ đã mệt mỏi / không lo lắng về mã có thể đọc được khi tôi viết điều này):

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

WTF, bạn nói! Vâng, có, nhưng có một vài điểm thực sự hữu ích ở đây. Trước hết, fixđối số đầu tiên của bạn thường phải là một hàm là trường hợp 'đệ quy' và đối số thứ hai là dữ liệu để hành động. Đây là mã giống như một hàm được đặt tên:

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

Nếu bạn vẫn còn bối rối thì có lẽ giai thừa sẽ là một ví dụ dễ dàng hơn:

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

Lưu ý đánh giá:

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

Ồ, bạn vừa nhìn thấy? Điều đó xđã trở thành một chức năng bên trong thenchi nhánh của chúng tôi .

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 -->

Ở trên bạn cần nhớ x = f x, do đó hai đối số của x 2ở cuối thay vì chỉ 2.

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

Và tôi sẽ dừng lại ở đây!


Câu trả lời của bạn là những gì thực sự có fixý nghĩa đối với tôi. Câu trả lời của tôi phần lớn phụ thuộc vào những gì bạn đã nói.
Dan Burton

@Thomas cả hai chuỗi giảm của bạn đều không chính xác. :) id xchỉ giảm thành x(sau đó giảm trở lại id x). - Sau đó, trong mẫu thứ 2 ( fact), khi xlần đầu tiên bị ép, giá trị kết quả được ghi nhớ và sử dụng lại. Việc tính toán lại (\recurse ...) xsẽ xảy ra với định nghĩa không chia sẻy g = g (y g) , không phải với định nghĩa chia sẻfix này . - Tôi đã thực hiện bản chỉnh sửa dùng thử ở đây - bạn có thể sử dụng nó, hoặc tôi có thể thực hiện chỉnh sửa nếu bạn chấp thuận.
Will Ness

thực tế, khi fix idđược giảm, let x = id x in xcũng buộc giá trị của ứng dụng id xbên trong letkhung (thunk), vì vậy nó giảm xuống let x = x in x, và điều này lặp lại. Hình như nó.
Sẽ không có

Chính xác. Câu trả lời của tôi là sử dụng lập luận cân bằng. Việc hiển thị mức giảm a la Haskell, liên quan đến thứ tự đánh giá, chỉ làm cho ví dụ nhầm lẫn mà không có bất kỳ lợi ích thực sự nào.
Thomas M. DuBuisson

1
Câu hỏi được gắn thẻ với cả haskell và letrec (tức là let đệ quy, có chia sẻ). Sự phân biệt giữa fixY là rất rõ ràng và quan trọng trong Haskell. Tôi không thấy điều tốt được phục vụ bằng cách hiển thị thứ tự giảm sai khi lệnh đúng thậm chí còn ngắn hơn, rõ ràng và dễ theo dõi hơn nhiều và phản ánh đúng những gì thực sự đang diễn ra.
Will Ness

3

Tôi hiểu nó như thế nào, nó tìm một giá trị cho hàm, sao cho nó xuất ra cùng một thứ mà bạn cung cấp cho nó. Vấn đề là, nó sẽ luôn chọn không xác định (hoặc một vòng lặp vô hạn, trong haskell, các vòng lặp không xác định và vô hạn giống nhau) hoặc bất kỳ thứ gì có nhiều undefineds nhất trong đó. Ví dụ: với id,

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

Như bạn có thể thấy, không xác định là một điểm cố định, vì vậy fixsẽ chọn điểm đó. Nếu thay vào đó bạn làm (\ x-> 1: x).

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

Vì vậy, fixkhông thể chọn không xác định. Để làm cho nó kết nối nhiều hơn một chút với các vòng lặp vô hạn.

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

Một lần nữa, một sự khác biệt nhỏ. Vậy điểm cố định là gì? Hãy để chúng tôi thử 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

Nó giống nhau! Vì đây là điểm cố định duy nhất, fixphải giải quyết trên đó. Xin lỗi fix, không có vòng lặp vô hạn hoặc không xác định cho bạn.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.