2017-07-03 28 views
9

Tôi đã gặp phải một số hành vi không mong muốn từ trình biên dịch F # gần đây. Tôi đã có thể tìm thấy một cách giải quyết khác, nhưng hành vi ban đầu cản trở tôi và tôi muốn xem liệu có ai có thể giúp tôi hiểu nguyên nhân gây ra nó hay không.Tại sao bộ biên dịch F # đôi khi có chức năng tổng quát không chính xác?

Một hàm mà tôi đã định nghĩa là không chung chung đã trở thành chung chung, điều này ảnh hưởng đến khả năng của hàm chia sẻ trạng thái giữa nhiều lần gọi. Tôi đơn giản hóa use-case của tôi xuống như sau:

let nextId = 
    let mutable i = 0 
    let help (key:obj) = 
    i <- i + 1 
    i 
    help 
nextId "a" // returns 1 
nextId "b" // also returns 1!!!! 

Tại sao nextId của loại 'a -> int thay vì obj -> int? Rõ ràng việc khái quát hóa cũng chịu trách nhiệm về lỗi mà nó trả về 1 nhiều lần, nhưng tại sao sự khái quát hóa lại xảy ra ngay từ đầu?

Lưu ý rằng nếu tôi định nghĩa nó mà không cần đặt tên cho các hàm lồng nhau, nó hoạt động như mong đợi trong việc đưa ra Id duy nhất:

let nextId = 
    let mutable i = 0 
    fun (key:obj) -> 
    i <- i + 1 
    i 
nextId "a" // returns 1 
nextId "b" // returns 2 

Nhưng ngay cả bí ẩn hơn, với định nghĩa này, F # Interactive không thể quyết định xem nextId là một (obj -> int) hoặc ('a -> int). Khi tôi lần đầu tiên xác định nó tôi nhận được

val nextId: (obj -> int)

nhưng nếu tôi chỉ đơn giản là eval

nextId 

tôi nhận được

val nó: (' a -> int)

Điều gì đang xảy ra ở đây và tại sao chức năng đơn giản của tôi lại tự động nói chung ized?

Trả lời

8

Tôi đồng ý rằng đây là hành vi khá bất ngờ. Tôi nghĩ lý do tại sao F # thực hiện việc khái quát hóa là nó xử lý help (khi trả lại) là fun x -> help x. Gọi một hàm mất obj có vẻ là một trường hợp trình biên dịch thực hiện khái quát hóa (vì nó biết rằng bất cứ điều gì có thể là obj). Tổng quát tương tự xảy ra, ví dụ, trong:

let foo (o:obj) = 1 
let g = fun z -> foo z 

Ở đây, g trở thành 'a -> int quá, giống như trong phiên bản đầu tiên của bạn. Tôi không hoàn toàn biết tại sao trình biên dịch thực hiện điều này, nhưng những gì bạn thấy có thể được giải thích bằng cách 1) xử lý helpfun x -> help x và 2) tổng quát về các cuộc gọi tham gia obj. Một điều khác đang xảy ra là cách F # xử lý các giá trị chung - giá trị chung thường có vấn đề trong ngôn ngữ ML (đó là toàn bộ "hạn chế giá trị"), nhưng F # cho phép nó trong một số trường hợp hạn chế - bạn có thể ví dụ: viết:

let empty = [] 

Điều này xác định giá trị chung loại 'a list. Các caveat là điều này được biên dịch như là một chức năng được gọi là mỗi khi bạn truy cập vào giá trị empty. Tôi nghĩ rằng hàm nextId đầu tiên của bạn được biên dịch theo cùng một cách - do đó, cơ thể được đánh giá mỗi khi bạn truy cập nó.

Điều này có thể không trả lời lý do tại sao nhưng tôi hy vọng nó cung cấp thêm một số mẹo về cách điều này xảy ra - và trong những trường hợp khác, hành vi bạn thấy có thể hợp lý!

5

Tôi không thể giải thích tại sao trình biên dịch quyết định khái quát trong kịch bản đầu tiên của bạn, nhưng cuối cùng sự khác biệt giữa nextId là loại obj -> int'a -> int là điều khiến hành vi dường như kỳ lạ ở đây.

Đối với những gì nó có giá trị, bạn có thể "ép" hành vi dự kiến ​​trong kịch bản đầu tiên của bạn với chưa một loại chú thích:

let nextId : obj -> int = 
    let mutable i = 0 
    let help (key:obj) = 
     i <- i + 1 
     i 
    help 

Bây giờ nếu bạn đặt những giá trị trong module (như trong gist này), biên dịch và kiểm tra việc lắp ráp trong ILSpy, bạn sẽ thấy rằng mã này là gần như giống hệt trừ nơi các tế bào ref cho các truy cập được khởi tạo:

  • trong trường hợp cụ thể, nextId là một tài sản có lãi suất một chức năng mà là inst antiated cùng với các tế bào ref trong initializer tĩnh cho các mô-đun, tức là tất cả các cuộc gọi đến nextId chia sẻ cùng một quầy,

  • Trong trường hợp chung chung, nextId là một chức năng chung năng suất một chức năng, và các tế bào ref được khởi tạo trong vòng cơ thể của nó, tức là bạn có một bộ đếm cho mỗi cuộc gọi đến nextId.

Vì vậy, mã phát ra trong trường hợp tổng quát thực sự có thể được trả lại trong F # với đoạn này:

let nextId() = 
    let mutable i = 0 
    fun key -> 
     i <- i + 1 
     i 

Điểm mấu chốt là nó sẽ làm cho tinh thần để phát ra một cảnh báo trình biên dịch khi bạn có một giá trị chung như thế này. Thật dễ dàng để tránh vấn đề khi bạn biết nó ở đó, nhưng đó là một trong những điều bạn sẽ không thấy đến nếu không.

Các vấn đề liên quan