2009-06-14 48 views
207

Tôi đi qua định nghĩa sau đây khi tôi cố gắng tìm hiểu Haskell bằng cách sử dụng một dự án thực sự để lái nó. Tôi không hiểu dấu chấm than trước mỗi đối số có nghĩa là gì và sách của tôi dường như không đề cập đến nó.Dấu chấm than có ý nghĩa gì trong khai báo Haskell?

data MidiMessage = MidiMessage !Int !MidiMessage 
+12

Tôi nghi ngờ rằng đây có thể là một câu hỏi rất phổ biến; Tôi nhớ rõ ràng đang tự hỏi về chính xác những điều tương tự như bản thân mình, cách trở lại khi nào. –

Trả lời

255

Đó là tuyên bố nghiêm ngặt. Về cơ bản, nó có nghĩa là nó phải được đánh giá với cái gọi là "dạng đầu bình thường yếu" khi giá trị cấu trúc dữ liệu được tạo ra. Hãy xem xét một ví dụ, để chúng ta có thể thấy chỉ là những gì này có nghĩa là:

data Foo = Foo Int Int !Int !(Maybe Int) 

f = Foo (2+2) (3+3) (4+4) (Just (5+5)) 

Chức năng f trên, khi đánh giá, sẽ trả về một "thunk": đó là, mã để thực hiện để tìm ra giá trị của nó . Tại thời điểm đó, một Foo thậm chí không tồn tại, chỉ là mã.

Nhưng tại một số điểm ai đó có thể cố gắng để nhìn vào bên trong nó, có lẽ thông qua một mô hình phù hợp:

case f of 
    Foo 0 _ _ _ -> "first arg is zero" 
    _   -> "first arge is something else" 

Điều này sẽ thực hiện đủ code để làm những gì nó cần, và không còn nữa. Vì vậy, nó sẽ tạo ra một Foo với bốn tham số (vì bạn không thể nhìn vào bên trong nó mà không có nó). Đầu tiên, vì chúng tôi đang thử nghiệm nó, chúng tôi cần phải đánh giá tất cả các cách để 4, nơi chúng tôi nhận ra nó không phù hợp.

Thứ hai không cần phải được đánh giá, bởi vì chúng tôi không thử nghiệm nó. Do đó, thay vì 6 được lưu trữ ở vị trí bộ nhớ đó, chúng tôi sẽ chỉ lưu trữ mã để có thể đánh giá sau này, (3+3). Điều đó sẽ biến thành 6 chỉ khi ai đó nhìn vào nó.

Thông số thứ ba, tuy nhiên, có ! ở phía trước, vì vậy được đánh giá đúng: (4+4) được thực hiện và 8 được lưu trữ ở vị trí bộ nhớ đó.

Thông số thứ tư cũng được đánh giá đúng. Nhưng đây là nơi nó hơi phức tạp một chút: chúng tôi đang đánh giá không hoàn toàn, nhưng chỉ cho hình dạng đầu bình thường yếu. Điều này có nghĩa là chúng tôi tìm hiểu xem đó là Nothing hoặc Just điều gì đó và lưu trữ điều đó, nhưng chúng tôi không tiến xa hơn nữa. Điều đó có nghĩa là chúng tôi lưu trữ không Just 10 nhưng thực tế là Just (5+5), để lại phần bên trong không được đánh giá. Điều này là quan trọng để biết, mặc dù tôi nghĩ rằng tất cả các tác động của điều này đi chứ không phải là phạm vi của câu hỏi này.

Bạn có thể chú thích đối số chức năng trong cùng một cách, nếu bạn kích hoạt phần mở rộng BangPatterns ngôn ngữ:

f x !y = x*y 

f (1+1) (2+2) sẽ trả lại thunk (1+1)*4.

+12

Điều này rất hữu ích. Tuy nhiên, tôi không thể tự hỏi liệu thuật ngữ Haskell có làm cho mọi thứ phức tạp hơn đối với mọi người hiểu với các thuật ngữ như "dạng đầu bình thường yếu", "nghiêm khắc" và vân vân hay không. Nếu tôi hiểu bạn một cách chính xác, nó có vẻ giống như! toán tử đơn giản có nghĩa là lưu trữ giá trị được đánh giá của một biểu thức thay vì lưu trữ một khối ẩn danh để đánh giá nó sau này. Đó có phải là một cách giải thích hợp lý hay là có cái gì đó nhiều hơn cho nó? – David

+61

@David Câu hỏi là cách xa để đánh giá giá trị. Biểu mẫu bình thường yếu đầu có nghĩa là: đánh giá nó cho đến khi bạn đạt đến hàm tạo ngoài cùng. Hình thức bình thường có nghĩa là đánh giá toàn bộ giá trị cho đến khi không còn thành phần chưa được đánh giá nào. Bởi vì Haskell cho phép tất cả các loại mức độ đánh giá, nó có một thuật ngữ phong phú để mô tả điều này. Bạn không có xu hướng tìm thấy những khác biệt này trong các ngôn ngữ chỉ hỗ trợ ngữ nghĩa theo giá trị. –

+7

@ David: Tôi đã viết một lời giải thích sâu hơn ở đây: [Haskell: Biểu mẫu bình thường đầu yếu là gì?] (Http://stackoverflow.com/questions/6872898/haskell-what-is-weak-head-normal- form/6889335 # 6889335). Mặc dù tôi không đề cập đến các mẫu chữ hoa hoặc chú thích nghiêm ngặt, chúng tương đương với việc sử dụng 'seq'. – hammar

23

Tôi tin rằng đó là chú thích nghiêm ngặt.

Haskell là một ngôn ngữ chức năng thuần túy và lười biếng, nhưng đôi khi chi phí của sự lười biếng có thể quá nhiều hoặc lãng phí. Vì vậy, để đối phó với điều đó, bạn có thể yêu cầu trình biên dịch đánh giá đầy đủ các đối số cho một hàm thay vì phân tích cú pháp xung quanh.

Có thêm thông tin trên trang này: Performance/Strictness.

+0

Hmm, ví dụ trên trang mà bạn tham chiếu dường như nói về việc sử dụng! khi gọi một hàm. Nhưng điều đó có vẻ khác với việc đưa nó vào một tuyên bố kiểu. Tôi đang thiếu gì? – David

+0

Tạo một thể hiện của một loại cũng là một biểu thức. Bạn có thể nghĩ về các hàm tạo kiểu như các hàm trả về các cá thể mới của kiểu được chỉ định, cho các đối số được cung cấp. –

+1

Thực tế, đối với tất cả các ý định và mục đích, hãy gõ constructors * là * functions; bạn có thể áp dụng chúng một phần, chuyển chúng sang các chức năng khác (ví dụ: 'map Chỉ [1,2,3]' để nhận [Chỉ 1, Chỉ 2, Chỉ 3]) và cứ thế. Tôi thấy hữu ích khi nghĩ về khả năng phù hợp với mô hình với họ cũng như một cơ sở hoàn toàn không liên quan. –

67

Một cách đơn giản để thấy sự khác biệt giữa các đối số hàm tạo nghiêm ngặt và không nghiêm ngặt là cách chúng hoạt động khi chúng không được xác định.Với

data Foo = Foo Int !Int 

first (Foo x _) = x 
second (Foo _ y) = y 

Kể từ khi lập luận không chặt chẽ là không được đánh giá bởi second, đi qua trong undefined không gây ra một vấn đề:

> second (Foo undefined 1) 
1 

Nhưng lập luận chặt chẽ không thể undefined, ngay cả khi chúng tôi không sử dụng giá trị:

> first (Foo 1 undefined) 
*** Exception: Prelude.undefined 
+0

Điều này là tốt hơn so với câu trả lời của Curt Sampson vì nó thực sự giải thích các hiệu ứng người dùng quan sát được, biểu tượng '!' Có, chứ không phải là delving vào các chi tiết thực hiện nội bộ. –

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