2015-07-27 19 views
15

Các Prelude cho thấy ví dụ cho takedrop với lập luận tiêu cực:Tại sao `take` và` drop` được xác định cho các đối số phủ định?

take (-1) [1,2] == [] 
drop (-1) [1,2] == [1,2] 

Tại sao có những định nghĩa theo cách mà họ đang có, khi ví dụ x !! (-1) thực hiện điều "an toàn" hơn và treo? Nó có vẻ giống như một cách hackish và rất không giống như Haskell để làm cho tổng số hàm này, ngay cả khi đối số không có ý nghĩa. Có một số triết lý thiết kế lớn hơn đằng sau điều này mà tôi không nhìn thấy? Hành vi này có được đảm bảo theo tiêu chuẩn hay không, hay đây là cách mà GHC quyết định thực hiện nó?

+8

Có những điều hợp lý và thường hữu ích cần làm khi đối số vượt quá giới hạn: "trả về toàn bộ danh sách gốc" hoặc "trả về danh sách trống". Bạn không thể xác định một cách hợp lý '(!! (-1))' ở tất cả, không có giá trị dự phòng để trả lại. –

+8

Thực tế thú vị: ngữ nghĩa thay đổi giữa báo cáo Haskell 98 và 2010. [Ví dụ thực hiện] (https://www.haskell.org/onlinereport/list.html) trong 98 được sử dụng 'lỗi' nếu danh sách không trống và' n' là số âm, trong khi biến thể năm 2010 cho thấy giá trị âm có tác dụng tương tự như 'take 0'. – Zeta

+1

@Zeta: đó là điều hấp dẫn, tuyệt vời! – Lynn

Trả lời

13

Sẽ có một lý do chính đáng để thực hiện take một phần: có thể đảm bảo rằng danh sách kết quả, nếu có, luôn có số lượng phần tử được yêu cầu.

Hiện tại, take đã vi phạm điều này theo một hướng khác: khi bạn cố gắng lấy nhiều yếu tố hơn trong danh sách, chỉ cần nhiều nhất có, tức là ít hơn yêu cầu. Có lẽ không phải là điều thanh lịch nhất để làm, nhưng trong thực tế điều này có xu hướng làm việc ra khá hữu ích.

Các bất biến chính cho take được kết hợp với drop:

take n xs ++ drop n xs ≡ xs 

và điều đó cũng đúng ngay cả khi n là tiêu cực.

Một lý do chính đáng không để kiểm tra độ dài của danh sách là nó làm cho các chức năng thực hiện độc đáo trên danh sách vô hạn lười biếng: ví dụ,

take hugeNum [1..] ++ 0 : drop hugeNum [1..] 

ngay lập tức sẽ cho 1 như các yếu tố kết quả đầu tiên. Điều này sẽ không thể thực hiện nếu takedrop trước tiên phải kiểm tra xem có đủ yếu tố trong đầu vào hay không.

+0

Chấp nhận câu trả lời này, bởi vì 'take' đã vi phạm điều này theo một hướng khác theo cách * hữu ích * là một đối số khá tốt: Tôi không thể hiểu tại sao bạn muốn dựa vào cách' take (-1) ' cư xử, nhưng tôi đặt cược làm cho nó phù hợp với cách bạn mô tả là một phần của ý tưởng. – Lynn

6

Tôi nghĩ đó là vấn đề về lựa chọn thiết kế ở đây.

Định nghĩa hiện nay đảm bảo rằng tài sản

take x list ++ drop x list == list 

giữ cho bất kỳ x, bao gồm cả những tiêu cực cũng như những người lớn hơn length list.

Tuy nhiên, tôi có thể thấy giá trị trong một biến thể của take/drop lỗi nào xảy ra: đôi khi tai nạn được ưu tiên cho kết quả sai.

+0

Tất nhiên, một phần thực hiện sẽ vẫn giữ nguyên '(++). (take &&& drop) ≡ id' tài sản. – leftaroundabout

+0

@leftaroundabout Không phải LHS sẽ sụp đổ? (BTW, phương trình của bạn không gõ kiểm tra) – chi

+1

là mã giả. Ý tôi là, nếu 'drop n xs' và' take n xs' đều mang lại 'Nothing' khi' n ∉ [0 .. length xs-1] ', thì chúng ta sẽ luôn có' liftA2 (++) (lấy n xs) (thả n xs) ≡ Chỉ xs'. – leftaroundabout

4

x !! (-1) hiện "an toàn hơn" điều và treo

Crashing không phải là an toàn. Thực hiện chức năng không hoàn toàn phá hủy khả năng của bạn để giải thích về hành vi của một hàm dựa trên loại của nó.

Chúng ta hãy tưởng tượng rằng takedrop đã có hành vi "bị lỗi trên âm".Hãy xem xét loại của họ:

take, drop :: Int -> [a] -> [a] 

Một điều loại này chắc chắn không cho bạn biết rằng chức năng này có thể sụp đổ! Sẽ hữu ích nếu bạn sử dụng một ngôn ngữ tổng thể, mặc dù chúng tôi không - ý tưởng được gọi là fast and loose reasoning - nhưng để có thể thực hiện điều đó, bạn phải tránh sử dụng (và viết) các hàm không tổng nhiều nhất có thể.

Việc cần làm, sau đó, về các hoạt động có thể không thành công hoặc không có kết quả? Các loại là câu trả lời! Một thực sự an toàn biến thể của (!!) sẽ có một loại mà mô hình các trường hợp thất bại, như:

safeIndex :: [a] -> Int -> Maybe a 

Đây là một lợi thế cho các loại (!!),

(!!) :: [a] -> Int -> a 

nào, bằng cách quan sát đơn giản, có thể không có (tổng số) cư dân - bạn không thể "phát minh" một a nếu danh sách trống!

Cuối cùng, hãy quay lại takedrop. Mặc dù kiểu của họ không nói đầy đủ về hành vi, cùng với tên của họ (và lý tưởng là một vài thuộc tính QuickCheck), chúng tôi có một ý tưởng khá hay. Như những người phản ứng khác đã chỉ ra, hành vi này là thích hợp trong nhiều trường hợp. Nếu bạn thực sự là cần phải từ chối đầu vào độ dài phủ định, bạn không phải chọn giữa không tổng (bị rơi) hoặc khả năng hành vi đáng ngạc nhiên (chấp nhận độ dài phủ định) - mô hình kết quả có thể có trách nhiệm với các loại.

loại này làm cho nó rõ ràng rằng không có "kết quả" cho một số đầu vào:

takePos, dropPos :: Int -> [a] -> Maybe [a] 

Vẫn còn tốt hơn, loại này sử dụng các số tự nhiên; Các hàm với loại này thậm chí không thể là được áp dụng cho số âm!

takeNat, dropNat :: Nat -> [a] -> [a] 
+2

Tôi hiểu lý do của bạn, và tôi thực sự cố gắng tránh tổng số chức năng vì những lý do mà bạn đã đề cập, nhưng tôi vẫn đứng trước sự thật rằng tôi muốn có mọi thứ đi * rõ ràng là hoàn toàn sai * khi tôi làm điều gì đó kỳ quặc như 'take (- 1) [5] 'hơn là họ âm thầm trả về một giá trị" đủ gần "với những gì tôi có thể * muốn. Tôi thấy nó tốt hơn thế nào trong một số trường hợp, nhưng có những trường hợp khác mà 'length (take n xs) == n' là một định luật hợp lệ sẽ thực sự tốt đẹp! – Lynn

+2

So sánh tình huống này với 'đuôi': bạn có thể xác định hoàn toàn' đuôi [] = [] '(tức là' đuôi = thả 1') và nó sẽ làm những gì bạn muốn ... đôi khi. Nhưng có một [lý do chính đáng tại sao nó không] (https://en.wikipedia.org/wiki/Principle_of_least_astonishment). (Tất nhiên, các loại phụ thuộc là giải pháp "đẹp nhất" đối với các quyết định như thế này, nhưng đó là một câu chuyện hoàn toàn khác ...) – Lynn

+1

Tôi ngạc nhiên hơn khi một chức năng làm điều gì đó mà loại đó nói không nên làm, chẳng hạn như đâm :) Bạn không cần phải lựa chọn giữa tổng thể và ít ngạc nhiên nhất - bạn chỉ cần các hàm có các kiểu hữu ích, ví dụ 'betterTail :: [a] -> Có thể [a]'. – frasertweedale

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