let rec nth (list, i) =
match list, i with
| x::xs, 0 -> x
| x::xs, i -> nth(xs, i-1)
| [], _ -> ()
Chức năng này thực sự sẽ có (không mong muốn) chữ ký bạn đề cập:
val nth : unit list * int -> unit
Tại sao? Nhìn vào bên phải của ba quy tắc của bạn. Nếu không phải là ()
, bạn không thể nói loại giá trị cụ thể nào mà hàm của bạn trả về. Nhưng ngay sau khi bạn thêm quy tắc cuối cùng, F # sẽ thấy biểu thức ()
(có loại unit
) và từ đó có thể suy ra loại trả về của hàm của bạn; mà bây giờ không còn chung chung nữa. Vì bất kỳ hàm nào chỉ có thể có một kiểu trả về cố định, nó sẽ suy ra rằng x
, xs
cũng liên quan đến loại unit
, dẫn đến chữ ký ở trên.
Như đã lưu ý bởi kvb, bạn muốn đôi khi trả lại giá trị và trong danh sách đầu vào trống, bạn không muốn trả về bất kỳ giá trị nào ... nghĩa là giá trị trả về của bạn phải là 'a option
(cũng có thể là . viết như option<'a>
btw)
let rec nth (list, i) =
match list, i with
| x::xs, 0 -> Some(x)
| x::xs, i -> nth(xs, i-1) // <-- nth already returns an 'a option,
| [], _ -> None // no need to "wrap" it once more
Bây giờ chữ ký báo cáo có vẻ đúng:
val nth : 'a list * int -> 'a option
về bạn se câu hỏi cond, tôi thừa nhận rằng tôi không thể trả lời đầy đủ vì tôi vẫn là một F # newbie bản thân mình. Một gợi ý, mặc dù: Nếu bạn lấy hàm trên ở dạng chính xác của nó (phiên bản chung trả về số 'a option
), bạn thực sự không thể không kiểm tra tất cả các giá trị trả về:
Tại sao? Bởi vì nếu bạn muốn để có được giá trị trả về thực tế (tên x
trong mã chỉ được hiển thị), bạn cần phải "giải nén" nó sử dụng một khối match
:
let result = nth (someList, someIndex)
match result with
| Some(x) -> ...
| None -> ...
Và kể từ khi quy tắc của bạn nên luôn luôn đầy đủ (kẻo trình biên dịch phàn nàn), bạn sẽ tự động phải thêm quy tắc để kiểm tra khả năng của None
.
Trình biên dịch sẽ thực sự buộc bạn cũng nên cân nhắc điều gì sẽ xảy ra trong trường hợp "lỗi"; bạn không được phép quên nó. Bạn chỉ có thể chọn cách đối phó với nó!
(Tất nhiên, ngay sau khi bạn làm lập trình .NET và phải đối phó với các loại có thể được null
, mọi thứ có thể trông hơi khác một chút, vì null
không phải là một khái niệm có nguồn gốc của F #.)
Đề xuất cải tiến thêm: Nếu bạn thay đổi cách chức năng nth
của bạn chấp nhận đối số của nó, bạn có khả năng áp dụng một phần, nghĩa làmà bạn có thể sử dụng nó với các nhà điều hành pipelining:
let rec nth i list = // <-- swap order of arguments, don't pass them in
... // as a tuple but as two separate arguments
Bây giờ bạn có thể làm điều này:
someList
|> nth someIndex
Hoặc này:
let third = nth 2
someList |> third
Nếu, mặt khác, chỉ có chức năng của bạn chấp nhận một tuple, điều đó sẽ không hoạt động. Vì vậy, hãy xem xét nếu bạn thực sự cần tham số tuple: Trong trường hợp này, nó thực sự hạn chế tính linh hoạt và nhiều hơn nữa, "ý nghĩa"/nội dung của hai tham số không cho thấy chúng luôn được giữ và xuất hiện cùng nhau. Do đó, tôi khuyên bạn không nên sử dụng một bộ tuple trong trường hợp này.
"mức độ nghiêm ngặt là tỷ lệ thuận với số lượng nhà phát triển có thể gọi mã của bạn" Tóm tắt tuyệt vời. – TechNeilogy
Điều này nghe có vẻ hơi phức tạp, nhưng bạn không bao giờ có thể sai với mã "đá rắn". Sau khi tất cả, nó không thường xuyên bạn có thể đảm bảo làm thế nào mã của bạn sẽ được sử dụng, hoặc bởi ai, trong tương lai. – Daniel
@Daniel, Đúng, nhưng có một chi phí liên quan đến quyết định này, trong cả thời gian phát triển và bảo trì. Nếu mọi phương pháp đơn lẻ đều là hoang tưởng này, bạn sẽ kết thúc với một cơ sở mã có đầy đủ bản mẫu, cũng như các nhà phát triển không có tiền. –