2011-12-21 28 views
16

theo Haskell 2010 report, init được định nghĩa như sau:Tại sao init là một phần chức năng?

init    :: [a] -> [a] 
init [x]   = [] 
init (x:xs)  = x : init xs 
init []   = error "Prelude.init: empty list" 

base-4.4.1.0 định nghĩa nó tương tự. Đối với tôi, có vẻ như nó hoàn toàn có thể chấp nhận và trực quan để có:

init [] = [] 

có thể làm cho init tổng chức năng. Kể từ khi định nghĩa này được đưa vào báo cáo haskell 2010, tôi đoán rằng có những đối số cho nó. Đó có phải là trường hợp hay nó được định nghĩa theo cách đó vì truyền thống và khả năng tương thích ngược?

+11

init phải trả về tất cả trừ phần tử cuối cùng của danh sách. Danh sách trống không có phần tử cuối cùng, vì vậy không có init nào. Thêm vào đó, bất biến 'xs == init xs ++ [last xs]' sẽ không còn giữ nếu init được định nghĩa theo cách bạn mô tả. –

+0

@Niklas Nhưng, tương tự như bạn có thể nói, init nên trả về tất cả các phần tử không phải là phần tử cuối cùng, không có các phần tử như vậy vì vậy chúng ta nên trả về danh sách rỗng. – HaskellElephant

+0

@Niklas, nó sẽ không giữ? Ý tôi là nếu cuối cùng [] = [] –

Trả lời

19

Lý do tương tự tail [] không phải là []; nó phá vỡ các bất biến mà xác định các chức năng này. Chúng tôi có thể xác định headtail bằng cách nói rằng nếu headtail được xác định cho một giá trị xs, thì xs ≡ head xs : tail xs.

Như Niklas đã chỉ ra, bất biến chúng tôi muốn xác định initlastxs ≡ init xs ++ [last xs]. Đúng là last cũng không được xác định trên danh sách trống, nhưng tại sao nên last được xác định nếu init không thể? Cũng giống như sai nếu một trong số head hoặc tail được xác định trên đầu vào trong khi số khác không được, initlast là hai mặt của cùng một đồng tiền, chia danh sách thành hai giá trị tương đương với giá trị ban đầu.

Đối với một cái nhìn "thực tế" hơn (mặc dù có khả năng suy luận về các chương trình về bất biến hữu ích về các hoạt động mà họ sử dụng có rất lợi ích thiết thực!), Một thuật toán có sử dụng init có thể sẽ không cư xử một cách chính xác trên một danh sách trống và nếu init hoạt động theo cách đó, nó sẽ hoạt động để ẩn các lỗi được tạo. Tốt hơn là nên bảo đảm an toàn cho các đầu vào của nó để xem xét các trường hợp cạnh như danh sách trống khi được sử dụng, đặc biệt là khi không rõ ràng giá trị được đề xuất để trả lại trong các trường hợp đó là hợp lý.

Dưới đây là một previous Stack Overflow question về headtail, bao gồm cả lý do tại sao tail [] không chỉ trở []; các câu trả lời sẽ giúp giải thích tại sao những bất biến này lại quan trọng đến thế.

+0

Tôi nghĩ rằng bạn có nghĩa là "tại sao nên' init' được định nghĩa khi 'last' không thể được?". – HaskellElephant

5

Thật là khủng khiếp khi có init [] = [], đặc biệt là nó sẽ phá vỡ các bất biến quan trọng như xs == init xs ++ [last xs]length xs == 1 + length (init xs) và do đó ẩn lỗi.

11

Bởi vì tôi tìm thấy những câu hỏi thú vị, tôi quyết định viết ra những suy nghĩ của tôi về đề tài này:

logic

Giả sử chúng ta định nghĩa init xs là "danh sách, rằng, nếu bạn đặt last xs trên nó cuối cùng, tương đương với xs.Điều này tương đương với: init xs là danh sách không có phần tử cuối cùng của nó.Đối với xs == [], không tồn tại thành phần cuối cùng, do đó, init [] phải không xác định.

Bạn có thể thêm một trường hợp đặc biệt cho điều này, như trong "nếu xs là danh sách rỗng, sau đó init xs là danh sách rỗng, nếu không init xs là danh sách, rằng, nếu bạn đặt last xs về kết thúc của nó, là bình đẳng đến xs ". Chú ý cách này tiết kiệm hơn và ít sạch hơn. Chúng tôi giới thiệu thêm sự phức tạp, nhưng những gì cho?

Intuitive

init [1,2,3,4] == [1,2,3] 
init [1,2,3] == [1,2] 
init [1,2]  == [1] 
init [1]  == [] 
init []  == ?? 

Lưu ý cách chiều dài của danh sách ở phía bên tay phải của các phương trình giảm cùng với chiều dài của phía bên trái. Với tôi, loạt bài này không thể được tiếp tục một cách hợp lý, bởi vì danh sách ở phía bên phải sẽ phải có một kích thước tiêu cực!

thực

Như những người khác đã chỉ ra, việc xác định một trường hợp đặc biệt xử lý cho init hoặc tail để xem danh sách rỗng như một cuộc tranh cãi, có thể giới thiệu khó chỗ sai sót trong những tình huống mà các chức năng có thể không có hợp lý kết quả cho danh sách trống, nhưng vẫn không tạo ra ngoại lệ cho nó!

Hơn nữa, tôi có thể nghĩ không có thuật toán nào thực sự là lợi thế khi có init [] đánh giá là [], vậy tại sao lại giới thiệu độ phức tạp thêm? Lập trình là tất cả về sự đơn giản và đặc biệt là Haskell là tất cả về sự tinh khiết, bạn sẽ không đồng ý?

+3

Có một sự tiếp nối rõ ràng và hợp lý của chuỗi trực quan của bạn, và đó là nhận ra rằng một trường hợp rõ ràng không xác định cần phải được đại diện như vậy. Kiểu đúng cho 'init' là như vậy' [a] -> Có thể [a] ', với' init [] = Nothing'. –

+0

Vâng, nhưng điều đó có nghĩa là chúng ta cần phải xử lý trường hợp đặc biệt bất cứ khi nào chúng ta gọi là 'init' (ví dụ bằng cách gói mọi lời gọi thành 'fromJust') ... Tôi không thích điều này lắm. –

+3

Cái gì? Không, 'fromJust' là một chức năng hoàn toàn khủng khiếp và không nên tồn tại chút nào. Điều đúng đắn cần làm là xử lý mọi trường hợp và không viết một phần chức năng ở nơi đầu tiên. –

8

Vấn đề thực sự ở đây là init, như được định nghĩa, không thể hoạt động; đó là chức năng một phần vốn có, giống như headtail. Tôi không vui mừng về việc có bao nhiêu chức năng cố tình một phần tồn tại trong các thư viện chuẩn, nhưng đó là một vấn đề khác.

Kể từ init như được đưa ra không thể được vớt, chúng tôi có hai lựa chọn:

  • Hãy giải thích rằng đó là một bộ lọc trên danh sách đưa ra, giữ tất cả các yếu tố mà không phải là yếu tố cuối cùng, và từ bỏ bất biến đối với lengthlast. Điều này có thể được viết là reverse . drop 1 . reverse, điều này gợi ý khái quát hóa rõ ràng. Chúng tôi cũng có thể giới thiệu một đối tác tương tự cho take nếu muốn.

  • Nhận ra rằng init xác định một phần chức năng và cung cấp cho đúng loại, [a] -> Maybe [a]. Lỗi sau đó có thể được thay thế chính xác bằng Nothing.

+0

Một khái quát hóa rõ ràng như 'inits' :) – ehird

+0

@ehird, cũng' inits' là phù hợp hơn với 'init [] = []' kể từ 'inits [] == [[]]'. Theo cách của nó, init duy nhất của [] là [], nếu nó gắn với 'init [] = undefined', nó phải là' inits [] == [] '. – HaskellElephant

+0

Hmm, tôi không đồng ý. 'length (inits xs) ≡ length xs + 1', do đó,' length (inits []) ≡ 1', và phần tử duy nhất nó có thể có là '[]'. – ehird

1

Đó là truyền thống. Vào thời điểm đó, họ đã lựa chọn tùy ý, và bây giờ mọi người quen với nó.

Họ đã thực hiện init tương tự như last, để cả hai đều không thành công trên danh sách trống. (tailhead là một cặp khác như thế.)

Nhưng họ có thể đã chọn điều ngược lại, như bạn đề xuất. Prelude có thể dễ dàng chứa hàm tail' = drop 1, cũng như hàm init' phù hợp, cho phép danh sách trống.Tôi không nghĩ rằng đây sẽ là vấn đề - tôi không thuyết phục bởi tất cả các cuộc nói chuyện về bất biến và định lý tự do. Nó chỉ có nghĩa là init' là một chút không giống với bạn đồng hành của nó last; đó là tất cả.

(lasthead là một câu chuyện khác: chữ ký của họ là [a] -> a, vì vậy họ phải trả lại một phần tử, và họ không thể đưa ra một ra khỏi không khí mỏng Ngược lại, các loại inittail là tất nhiên [a] -> [a]. , có nghĩa là họ có thể và thực hiện các danh sách trống.)

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