2011-01-04 26 views
6

Hãy xem xét các chức năng,trong haskell, tại sao tôi cần phải xác định ràng buộc kiểu, tại sao trình biên dịch không thể tìm ra chúng?

add a b = a + b 

này hoạt động:

*Main> add 1 2 
3 

Tuy nhiên, nếu tôi thêm một loại chữ ký xác định rằng tôi muốn thêm thứ cùng loại:

add :: a -> a -> a 
add a b = a + b 

Tôi gặp lỗi:

test.hs:3:10: 
    Could not deduce (Num a) from the context() 
     arising from a use of `+' at test.hs:3:10-14 
    Possible fix: 
     add (Num a) to the context of the type signature for `add' 
    In the expression: a + b 
    In the definition of `add': add a b = a + b 

Vì vậy, GHC rõ thể suy ra rằng tôi cần Num loại hạn chế, vì nó chỉ nói với tôi:

add :: Num a => a -> a -> a 
add a b = a + b 

trình.

Tại sao GHC yêu cầu tôi thêm ràng buộc loại? Nếu tôi đang làm lập trình chung chung, tại sao nó không thể làm việc cho bất cứ điều gì mà biết làm thế nào để sử dụng các nhà điều hành +?

Trong C++ template lập trình, bạn có thể làm điều này một cách dễ dàng:

#include <string> 
#include <cstdio> 

using namespace std; 

template<typename T> 
T add(T a, T b) { return a + b; } 

int main() 
{ 
    printf("%d, %f, %s\n", 
      add(1, 2), 
      add(1.0, 3.4), 
      add(string("foo"), string("bar")).c_str()); 
    return 0; 
} 

Những con số trình biên dịch ra các loại của các đối số để add và tạo ra một phiên bản của hàm cho loại đó. Dường như có một sự khác biệt cơ bản trong cách tiếp cận của Haskell, bạn có thể mô tả nó và thảo luận về sự cân bằng? Dường như với tôi như nó sẽ được giải quyết nếu GHC chỉ đơn giản là điền vào các ràng buộc loại cho tôi, vì nó rõ ràng là quyết định nó là cần thiết. Tuy nhiên, tại sao các ràng buộc loại ở tất cả? Tại sao không chỉ biên dịch thành công miễn là hàm chỉ được sử dụng trong ngữ cảnh hợp lệ trong đó các đối số nằm trong Num?

+6

Tại sao nó nên biến chữ ký kiểu thành một cái gì đó ít chung hơn bằng cách thêm một ràng buộc * khi bạn nói rõ ràng rằng bạn không muốn ràng buộc * (bằng cách khai báo 'add :: a -> a -> a')? Cũng lưu ý rằng trong Haskell, không có điều có '(+)' quá tải nhưng không phải là một thể hiện của 'Num' (vì' (+) 'là trong' Num', do đó, để quá tải nó, bạn phải khai báo một 'Num' instance). – delnan

Trả lời

14

Nếu bạn không muốn chỉ định loại của hàm, chỉ cần bỏ nó ra và trình biên dịch sẽ suy ra các kiểu tự động. Nhưng nếu bạn chọn chỉ định các loại, chúng để chính xác.

+3

Tôi nghĩ bạn đã đánh vào đây. Nếu bạn muốn chỉ định kiểu, thì bạn không muốn trình biên dịch bỏ qua đặc tả của bạn (hoặc "sửa" nó) –

15

toàn bộ điểm loại là có cách chính thức để khai báo đúng cách và sai cách sử dụng hàm. Một loại (Num a) => a -> a -> a mô tả chính xác những gì được yêu cầu của các đối số. Nếu bạn bỏ qua ràng buộc lớp, bạn sẽ có một hàm tổng quát hơn có thể được sử dụng (sai) ở nhiều nơi hơn.

Và nó không chỉ ngăn cản bạn chuyển các giá trị không Num tới add. Ở khắp mọi nơi chức năng đi, loại là chắc chắn để đi. Hãy xem xét điều này:

add :: a -> a -> a 
add a b = a + b 
foo :: [a -> a -> a] 
foo = [add] 
value :: [String] 
value = [f "hello" "world" | f <- foo] 

Bạn muốn trình biên dịch từ chối điều này, đúng không? sao làm được vậy? Bằng cách thêm các ràng buộc của lớp và kiểm tra xem chúng không bị xóa, ngay cả khi bạn không trực tiếp đặt tên cho hàm đó.

Điều gì khác biệt trong phiên bản C++? Không có ràng buộc về lớp. Trình biên dịch thay thế int hoặc std::string cho T, sau đó cố gắng biên dịch mã kết quả và tìm kiếm toán tử + phù hợp mà nó có thể sử dụng.Hệ thống mẫu "lỏng hơn", vì nó chấp nhận nhiều chương trình không hợp lệ hơn, và đây là một triệu chứng của nó là một giai đoạn riêng biệt trước khi biên dịch. Tôi sẽ yêu thích để sửa đổi C++ để thêm các ngữ nghĩa <? extends T> từ Generics của Java. Chỉ cần tìm hiểu hệ thống kiểu và nhận ra rằng đa hình tham số là "mạnh hơn" so với các mẫu C++, cụ thể là nó sẽ từ chối nhiều chương trình không hợp lệ.

+0

Tôi vẫn không thấy lý do tại sao trình biên dịch không thể suy ra giới hạn để thêm vào dòng thứ 2, tuyên truyền nó thành foo trong dòng thứ 4 và phát hiện lỗi trong dòng thứ 6. Nó có thể không hữu ích lắm, nhưng về kỹ thuật có thể, đúng không? – adamax

+18

@adamax: Có, nó là về mặt kỹ thuật có thể, nhưng sau đó làm thế nào để tôi thực sự khẳng định với trình biên dịch rằng một hàm có loại không bị giới hạn 'a -> a -> a'?Đây là một kiểu hoàn toàn có ý nghĩa, và nếu tôi nói với trình biên dịch rằng hàm của tôi có kiểu như vậy, tôi thực sự không muốn nó âm thầm tự nói "OK, anh ấy nói" a -> a -> a " nhưng tôi biết anh ấy thực sự ngụ ý Foo a => a -> a -> a ". Nếu nó nói rằng chính nó, tôi muốn nó nói với tôi rằng tôi đã sai, bởi vì tôi _was_. – mokus

+0

mokus: bình luận của bạn ở đây là thuyết phục hơn bất kỳ câu trả lời nào. "Nếu nó tự nói với mình, tôi muốn nó nói với tôi rằng tôi đã sai, bởi vì tôi là như vậy." Cảm ơn, điều đó thực sự làm cho nó rõ ràng. – Steve

12

Tôi nghĩ rằng bạn có thể bị vấp bởi "thơ ca trăng điên" của thông báo lỗi của GHC. Nó không nói rằng (là GHC) không thể suy ra ràng buộc (Num a). Có thể nói rằng ràng buộc (Num a) không thể được suy ra từ chữ ký loại của bạn, mà nó biết phải có từ việc sử dụng +. Do đó, bạn đang nói rằng hàm này có một kiểu tổng quát hơn trình biên dịch biết nó có thể có. Trình biên dịch không muốn bạn nói dối về các chức năng của bạn với thế giới!

Trong ví dụ đầu tiên bạn đưa ra, không có chữ ký loại, nếu bạn chạy :t add trong ghci, bạn sẽ thấy trình biên dịch biết rõ ràng rằng ràng buộc (Num a) là có.

Đối với mẫu của C++, hãy nhớ rằng chúng là mẫu cú pháp và chỉ được kiểm tra kiểu đầy đủ trong mỗi trường hợp khi chúng được sử dụng. Mẫu add của bạn sẽ hoạt động với bất kỳ loại nào miễn là, ở mỗi vị trí được sử dụng, có một toán tử + phù hợp và có lẽ chuyển đổi, để tạo mẫu thể hiện của mẫu. Không có đảm bảo nào có thể được thực hiện về mẫu cho đến lúc đó ... đó là lý do tại sao phần thân của mẫu phải "hiển thị" đối với mỗi mô-đun sử dụng nó.

Về cơ bản, tất cả C++ có thể làm là xác thực cú pháp của mẫu, và sau đó giữ nó xung quanh như một loại macro rất hợp vệ sinh. Trong khi Haskell tạo ra một chức năng thực sự cho add (để lại sang một bên rằng nó có thể chọn cũng tạo ra các loại chuyên môn cụ thể để tối ưu hóa).

+0

+1 cho cụm từ "thơ trăng điên". – dfeuer

3

Có những trường hợp trình biên dịch không thể tìm ra loại phù hợp cho bạn và nơi cần trợ giúp của bạn. Cân nhắc

f s = show $ read s 

Trình biên dịch nói:

Ambiguous type variable `a' in the constraints: 
Read a' arising from a use of `read' at src\Main.hs:20:13-18 
`Show a' arising from a use of `show' at src\Main.hs:20:6-9 
Probable fix: add a type signature that fixes these type variable(s) 

(đủ Strange, có vẻ như bạn có thể xác định chức năng này trong ghci, nhưng có vẻ như không có cách nào để thực sự sử dụng nó)

Nếu bạn muốn một số thứ như f "1" hoạt động, bạn cần để chỉ định loại:

f s = show $ (read s :: Int) 
+4

GHCi sử dụng các quy tắc "mặc định" lỏng hơn tiêu chuẩn Haskell chính thức chỉ định. Trong trường hợp này, nó sẽ chọn 'read :: String ->()', vì vậy bạn có thể sử dụng 'f' trên chuỗi"() ". Bất kỳ chuỗi nào khác sẽ không phân tích cú pháp. (Chỉnh sửa: tốt, ngoại trừ các chuỗi khác mà dụ() của đọc chấp nhận, chẳng hạn như "()") – mokus

+0

Cảm ơn bạn đã làm rõ! – Landei

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