2013-08-27 19 views
11

Tôi đã dạy bản thân mình F # gần đây, và tôi đến từ một nền tảng bắt buộc (C++/C#). Như một bài tập, tôi đã làm việc trên các chức năng có thể làm những thứ với ma trận, như thêm, nhân, nhận định thức, vv Mọi thứ diễn ra tốt đẹp trong vấn đề này, nhưng tôi thấy rằng có lẽ tôi không đưa ra quyết định tốt nhất khi xử lý đầu vào không hợp lệ, ví dụ:F #: Một số, Không, hoặc Ngoại lệ?

// I want to multiply two matrices 
let mult m1 m2 = 
    let sizeOK = validateDims m1 m2 

    // Here is where I am running to conceptual trouble: 
    // In a C# world, I would throw an exception. 
    if !sizeOK then 
    raise (InvalidOperationException("bad dimensions!") 
    else 
    doWork m1 m2 

Vì vậy, trong khi kỹ thuật này hoạt động, điều này có phù hợp với ngôn ngữ chức năng không? Là nó trong tinh thần của lập trình chức năng? Hoặc nó sẽ làm cho ý nghĩa hơn để viết lại nó như:

let mult m1 m2 = 
    let sizeOK = validateDims m1 m2 

    if !sizeOK then 
    None 
    else 
    Some doWork m1 m2 

Trong trường hợp này, tôi đang trở về một lựa chọn, trong đó bổ sung thêm thêm một lớp xung quanh ma trận, nhưng tôi cũng có thể sử dụng kết quả của hàm, ngay cả trong thất bại các trường hợp (Không) với kết hợp mẫu, v.v. tại một số điểm sau này trong chương trình. Vì vậy, có một thực hành tốt nhất cho các loại kịch bản? Lập trình chức năng sẽ làm gì?

+0

Tôi tin rằng sau này là nhiều hơn trong "tinh thần" của FP. Bạn có thể muốn xem xét đơn nguyên Có lẽ cũng như đó là một cách tiếp cận phổ biến để xử lý các yếu tố đầu vào xấu. –

+0

Như pad nói trong câu trả lời của mình tôi cũng thường thực hiện hai chức năng, một trong đó ném về lỗi và một chức năng thử giống hệt nhau trả về một Option/Choice thay vì ném. sau đó tôi sử dụng một trong đó làm cho mã cuối cùng của tôi trông đẹp nhất :) để xử lý Option/Choice dễ dàng hơn, bạn có thể muốn đọc "lập trình hướng tuyến đường sắt": http://fsharpforfunandprofit.com/posts/recipe-part2/ – stmax

Trả lời

8

tôi có xu hướng để tránh trường hợp ngoại lệ vì những lý do sau đây:

  • .NET exceptions are slow
  • Exceptions thay đổi dòng chảy kiểm soát các chương trình một cách bất ngờ, mà làm cho nó khó khăn hơn nhiều để suy luận về
  • Exceptions thường phát sinh trong các tình huống quan trọng trong khi bạn có thể không an toàn bằng cách sử dụng các tùy chọn.

Trong trường hợp của bạn, tôi sẽ làm theo F # lõi ước thư viện (ví dụ List.tryFindList.find, vv) và tạo ra cả hai phiên bản:

let tryMult m1 m2 = 
    let sizeOK = validateDims m1 m2 

    if not sizeOK then 
    None 
    else 
    Some <| doWork m1 m2 

let mult m1 m2 = 
    let sizeOK = validateDims m1 m2 

    if not sizeOK then 
    raise <| InvalidOperationException("bad dimensions!") 
    else 
    doWork m1 m2 

Ví dụ này không phải là ngoại lệ đủ để sử dụng ngoại lệ . Chức năng mult được bao gồm cho khả năng tương thích C#. Người nào đó đang sử dụng thư viện của bạn trong C# không có mẫu phù hợp để phân tách các tùy chọn dễ dàng.

Một hạn chế với các tùy chọn là chúng không đưa ra lý do tại sao hàm không tạo ra giá trị. Đó là quá mức cần thiết ở đây; thường Choice (hoặc Hoặc đơn nguyên trong nhiệm kỳ Haskell) là phù hợp hơn cho error handling:

let tryMult m1 m2 = 
    // Assume that you need to validate input 
    if not (validateInput m1) || not (validateInput m2) then 
    Choice2Of2 <| ArgumentException("bad argument!") 
    elif not <| validateDims m1 m2 then 
    Choice2Of2 <| InvalidOperationException("bad dimensions!") 
    else 
    Choice1Of2 <| doWork m1 m2 

Đó là một điều đáng tiếc rằng F # Core thiếu chức năng high-order để thao tác lựa chọn. Bạn có thể tìm thấy các chức năng đó trong thư viện FSharpX hoặc ExtCore.

3

Tôi có xu hướng đi theo các hướng dẫn sau đây:

Sử dụng ngoại lệ trong một chức năng đó là nghĩa vụ luôn luôn có một giá trị trả về, khi họ gặp khó khăn đột xuất. Điều này có thể, ví dụ: nếu các đối số không tuân theo hợp đồng cho hàm. Điều này có lợi thế là mã khách hàng trở nên đơn giản hơn.

Sử dụng tùy chọn khi hàm đôi khi có giá trị trả lại cho mục nhập hợp lệ. Điều này có thể, ví dụ: nhận được trên một bản đồ nơi một khóa hợp lệ có thể không tồn tại. Qua đó bạn buộc người dùng kiểm tra xem hàm có giá trị trả về hay không. Điều này có thể làm giảm lỗi, nhưng luôn luôn clutters mã khách hàng.

Trường hợp của bạn có phần ở giữa. Nếu bạn mong đợi nó chủ yếu được sử dụng ở những nơi mà kích thước là hợp lệ, tôi sẽ ném một ngoại lệ. Nếu bạn mong đợi mã máy khách thường gọi nó với thứ nguyên không hợp lệ, tôi sẽ trả về một tùy chọn. Tôi có lẽ sẽ đi với trước đây, vì nó là sạch hơn (xem bên dưới) nhưng tôi không biết hoàn cảnh của bạn:

// With exception 
let mult3 a b c = 
    mult (mult a b) c; 

// With option 
let mult3 a b c= 
    let option = mult a b 
    match option with 
    | Some(x) -> mult x b 
    | None -> None 

Disclaimer: Tôi không có kinh nghiệm chuyên nghiệp với chức năng lập trình, nhưng tôi là một TA trong chương trình F # ở cấp độ sau đại học.

2

Tôi thích câu trả lời ở trên nhưng tôi muốn thêm tùy chọn khác. Nó thực sự phụ thuộc như thế nào bất ngờ kết quả là và cho dù nó có ý nghĩa để tiến hành. Nếu đó là một sự kiện hiếm hoi và người gọi có thể không có kế hoạch thất bại, thì một ngoại lệ hoàn toàn đáng kính. Mã để bắt ngoại lệ có thể có nhiều cấp ở trên và người gọi có thể không có kế hoạch thất bại. Nếu đó là một kết quả thực sự thường xuyên cho một hoạt động thất bại, Some/None là ok mặc dù nó cung cấp cho bạn chỉ là hai lựa chọn và không có cách nào để vượt qua một kết quả. Một lựa chọn khác là tạo ra một liên minh phân biệt các khả năng. Điều này buộc người gọi có khả năng khớp với các kết quả khác nhau, có thể mở rộng và không buộc bạn phải thực hiện mọi kết quả cùng loại dữ liệu.

ví dụ:

type MultOutcome = 
    | RESULT of Matrix 
    | DIMERROR 
    | FOOERROR of string 


let mult a b = 
    if dimensionsWrong then 
     DIMERROR 
    elif somethingElseIDoNotLike then 
     FOOERROR("specific message") 
    else 
     DIMRESULT(a*b) 


match mult x y with 
    | DIMERROR -> printfn "I guess I screwed up my matricies" 
    | FOOERROR(s) -> printfn "Operation failed with message %s" s 
    | DIMRESULT(r) -> 
     // Proceed with result r