2009-04-30 18 views
15

tôi đã viết chức năng follwing:Làm thế nào để tôi biết nếu một chức năng là đệ quy đuôi trong F #

let str2lst str = 
    let rec f s acc = 
     match s with 
     | "" -> acc 
     | _ -> f (s.Substring 1) (s.[0]::acc) 
    f str [] 

Làm thế nào tôi có thể biết nếu F # biên dịch biến nó thành một vòng lặp? Có cách nào để tìm ra mà không sử dụng Reflector (Tôi không có kinh nghiệm với Reflector và tôi không biết C#)?

Chỉnh sửa: Ngoài ra, có thể viết hàm đệ quy đuôi mà không cần sử dụng hàm bên trong hay là cần thiết cho vòng lặp cư trú?

Ngoài ra, có chức năng trong F # std lib để chạy một hàm đã cho một số lần, mỗi lần cho nó đầu ra cuối cùng làm đầu vào? Cho phép nói rằng tôi có một chuỗi, tôi muốn chạy một chức năng trên chuỗi sau đó chạy nó một lần nữa trên chuỗi kết quả và như vậy ...

+0

Xem thêm http://stackoverflow.com/questions/5809683/is-my-rec-function-tail-recursive – Brian

Trả lời

22

Thật không may là không có cách tầm thường. Không phải là quá khó để đọc mã nguồn và sử dụng các loại và xác định xem một cái gì đó là một cuộc gọi đuôi bằng cách kiểm tra (là nó 'điều cuối cùng', và không phải trong một khối 'thử'), nhưng mọi người thứ hai - tự mình và phạm sai lầm. Không có cách tự động đơn giản nào (ngoài việc kiểm tra mã được tạo).

Tất nhiên, bạn chỉ có thể thử chức năng của bạn trên một đoạn lớn dữ liệu thử nghiệm và xem liệu nó có thổi lên hay không. Trình biên dịch F # sẽ tạo ra các lệnh .tail IL cho tất cả các cuộc gọi đuôi (trừ khi cờ trình biên dịch tắt chúng được sử dụng - được sử dụng khi bạn muốn giữ khung ngăn xếp để gỡ lỗi), với ngoại lệ trực tiếp đệ quy đuôi chức năng sẽ được tối ưu hóa thành vòng lặp. (EDIT: Tôi nghĩ ngày nay trình biên dịch F # cũng không phát ra .tail trong trường hợp nó có thể chứng minh không có vòng lặp đệ quy thông qua trang web gọi này; đây là một tối ưu hóa cho rằng opcode .tail chậm hơn một chút trên nhiều nền tảng.)

'tailcall' là từ khóa dành riêng, với ý tưởng cho rằng phiên bản F # trong tương lai có thể cho phép bạn viết ví dụ

tailcall func args 

và sau đó nhận cảnh báo/lỗi nếu đó không phải là cuộc gọi đuôi.

Chỉ các hàm không tự động đệ quy đuôi (và do đó cần thêm tham số tích lũy) sẽ 'buộc' bạn vào thành ngữ 'hàm bên trong'.

Dưới đây là một mẫu mã của những gì bạn hỏi:

let rec nTimes n f x = 
    if n = 0 then 
     x 
    else 
     nTimes (n-1) f (f x) 

let r = nTimes 3 (fun s -> s^" is a rose") "A rose" 
printfn "%s" r 
+0

"tailcall" là thú vị, vì nó có bạn chỉ định trang web của cuộc gọi thay vì những gì có thể là một hữu ích hơn "tailrec" tuyên bố trên toàn bộ chức năng. Bạn có thể có chức năng "đệ quy một phần đuôi" không? –

+3

@StephenSwensen Bạn hoàn toàn có thể. Kiểm soát dòng chảy chi nhánh có thể dẫn đến một thay thế giữa một cuộc gọi đuôi và một cuộc gọi mà không phải là một cuộc gọi đuôi, và bạn chỉ có thể muốn kiểm tra một trong đó thực sự là một cuộc gọi đuôi tại thời gian biên dịch. 'let rec f x = nếu x> 0 thì f (1 + x) khác 0 + f (1 + x)' minh họa khái niệm, mặc dù rõ ràng nó sẽ không kết thúc. –

2

Tôi thích quy luật của Paul Graham formulates trong On Lisp: nếu có công việc còn lại để làm, ví dụ thao tác đầu ra cuộc gọi đệ quy, sau đó cuộc gọi không phải là đệ quy đuôi.