2012-06-30 34 views
5

OCaml's try .. with không cung cấp điều khoản finally như Java. Nó sẽ rất hữu ích, mặc dù, đặc biệt là khi đối phó với các tác dụng phụ. Ví dụ, tôi muốn mở một tập tin, chuyển tập tin mở sang một chức năng, và đóng nó lại. Trong trường hợp hàm tăng ngoại lệ, tôi phải bắt nó để có cơ hội đóng tập tin. Điều này ngày càng phức tạp khi nhiều tệp được mở và bản thân mở cũng có thể không thành công. Có một mô hình lập trình được thiết lập để giải quyết vấn đề này không?Mô phỏng thử-với-cuối cùng trong OCaml

Dưới đây là một hàm đơn giản minh họa sự cố. Chức năng f được áp dụng cho một kênh thuộc về một tệp nếu số path được cung cấp và stdin nếu không. Vì không có mệnh đề cuối cùng, close_in io xuất hiện hai lần.

let process f = function 
    | Some path -> 
     let io = open_in path in 
      ((try f io with exn -> close_in io; raise exn) 
      ; close_in io 
      ) 
    | None -> f stdin 

Trả lời

6

Có một mô hình lập trình thành lập để đối phó với điều này?

Có, các hàm bao bọc tách tài nguyên khỏi xử lý ngoại lệ. Những gì tôi làm là sử dụng một wrapper chung, unwind (một LISPism mà tôi chứ không phải sử dụng):

let unwind ~(protect:'a -> unit) f x = 
    try let y = f x in protect x; y 
    with e -> protect x; raise e 

Đây là một wrapper đơn giản mà không chiếm một cách chính xác cho trường hợp ngoại lệ được nêu ra trong protect; một wrapper kiểm tra đầy đủ đảm bảo rằng protect được gọi là chỉ một lần ngay cả khi nó tự không thể được Yaron Minski's, hoặc cái này mà tôi nghĩ là rõ ràng hơn một chút:

let unwind ~protect f x = 
    let module E = struct type 'a t = Left of 'a | Right of exn end in 
    let res = try E.Left (f x) with e -> E.Right e in 
    let() = protect x in 
    match res with 
    | E.Left y -> y 
    | E.Right e -> raise e 

Sau đó, tôi xác định các trường hợp cụ thể theo yêu cầu, ví dụ :

let with_input_channel inch f = 
    unwind ~protect:close_in f inch 

let with_output_channel otch f = 
    unwind ~protect:close_out f otch 

let with_input_file fname = 
    with_input_channel (open_in fname) 

let with_output_file fname = 
    with_output_channel (open_out fname) 

Lý do tôi chuyển thông số cho các chức năng cụ thể cho chương trình bậc cao hơn; cụ thể, bằng cách xác định một nhà điều hành ứng dụng à la Haskell, tôi có thể viết:

let() = with_output_file "foo.txt" $ fun otch -> 
    output_string otch "hello, world"; 
    (* ... *) 

với cú pháp không quá nặng. Đối với một ví dụ hơi tham gia nhiều hơn, hãy xem xét những điều sau đây:

let with_open_graph spec (proc : int -> int -> unit) = 
    unwind ~protect:Graphics.close_graph (fun() -> 
    proc (Graphics.size_x()) (Graphics.size_y()); 
    ignore (Graphics.wait_next_event [Graphics.Button_down]); 
    ignore (Graphics.wait_next_event [Graphics.Button_up])) 
    (Graphics.open_graph spec) 

mà có thể được sử dụng với một cuộc gọi như with_open_graph " 400x300" $ fun width height -> (*...*).

+2

Rất đẹp! Một chút sang một bên: Tôi giả định '$' được định nghĩa là 'let ($) f x = f x' nhưng điều này sẽ làm cho nó trái kết hợp trong khi nó là kết hợp đúng trong Haskell. Do đó 'print_int $ (+) 3 $ 4' không hoạt động trong OCaml. Đối với một toán tử liên kết thích hợp, một toán tử có thể định nghĩa 'let (@@) f x = f x'. –

+0

Nếu 'bảo vệ' đặt ra một ngoại lệ, nó có được thực thi hai lần không? Vì ngoại lệ bị bắt và 'bảo vệ' được thực thi lại. Xem thêm [Yaron Minsky thực hiện 'bảo vệ'] (http://caml.inria.fr/pub/ml-archives/caml-list/2003/07/5ff669a9d2be35ec585b536e2e0fc7ca.en.html). –

+1

@ChristianLindig: Có, mã ban đầu của tôi không xử lý các ngoại lệ kép. Xem chỉnh sửa của tôi cho một phiên bản "chức năng" mà tôi nghĩ rằng cải thiện trên Yaron của. –

2

Nó không được xây dựng trong OCaml theo như tôi biết, nhưng bạn có thể viết thư viện cho phép bạn mã hóa các mẫu như vậy. Thư viện mẫu là Catch me if you can, đây là một mã hóa lỗi đơn điệu. Here là hướng dẫn để thêm cấu trúc finally bằng cách sử dụng lập trình meta. Có lẽ có những cách tiếp cận khác.

+0

Cảm ơn những lời đề nghị. Tôi đã hy vọng cho một giải pháp thông minh trong ngôn ngữ cốt lõi hơn là dựa vào Camlp5. –

1

Đây là một tùy chọn. (Tôi đã gỡ bỏ các khớp trên path để đun sôi mã xuống một ví dụ rất nhỏ.)

let process f path = 
    let exn = ref None in 
    let io = open_in path in 
    (try f io with e -> exn := Some e); 
    close_in io; 
    match !exn with Some e -> raise e | None ->() 
+0

Ý tưởng ở đây là lưu trữ ngoại lệ để tránh trùng lặp mã sẽ đi vào khối cuối cùng. Một thiết kế thay thế sẽ là nắm bắt khối cuối cùng trong một hàm cục bộ và gọi nó từ hai vị trí. –

3

Từ cuốn sách Unix system programming in OCaml bởi Xavier Leroy và Didier Rémy trong chương Generalities

"Không có built-in Finalize xây dựng thử ... hoàn thiện bằng ngôn ngữ OCaml, nhưng nó có thể dễ dàng định nghĩa:"

let try_finalize f x finally y = 
    let res = try f x with exn -> finally y; raise exn in 
    finally y; 
    res 
1

Các OCaml bộ sưu tập Pin thư viện cung cấp hai chức năng có thể được sử dụng để cuối cùng

http://ocaml-batteries-team.github.io/batteries-included/hdoc/BatPervasives.html

val finally : (unit -> unit) -> ('a -> 'b) -> 'a -> 'b 

finally fend f x gọi f x và sau đó fend() ngay cả khi f x nêu lên một ngoại lệ.

val with_dispose : dispose:('a -> unit) -> ('a -> 'b) -> 'a -> 'b 

with_dispose dispose f x gọi f trên x, gọi dispose x khi f chấm dứt (hoặc với một giá trị trả về hoặc một ngoại lệ).

1

Nếu cả hai chức năng và khối cuối cùng đặt ra một ngoại lệ tôi thích để xem ngoại lệ ban đầu, vì vậy tôi sử dụng một cái gì đó như thế này:

type 'a result = OK of 'a | Exn of exn 
let result_of f x = try OK (f x) with e -> Exn e 

(** invokes [f x], and always releases [x] by invoking [release x] *) 
let do_with x release f = 
    let result = result_of f x in 
    let closed = result_of release x in 
    match result, closed with 
    | Exn e, _ -> raise e (* [f x] raised exception *) 
    | _, Exn e -> raise e (* [release x] raised exception *) 
    | OK r, OK() -> r (* all OK *)