Concatenation với (++)
Có lẽ tôi đang nghĩ đến sâu vào điều này, nhưng, như xa như tôi hiểu, nếu bạn cố gắng nối danh sách sử dụng (++)
ví dụ:
[1, 2, 3] ++ [4, 5]
(++)
phải duyệt qua danh sách bên trái hoàn chỉnh. Hãy xem số code of (++) làm cho nó tất cả rõ ràng hơn.
(++) :: [a] -> [a] -> [a]
(++) [] ys = ys
(++) (x:xs) ys = x : xs ++ ys
Do đó, nó sẽ được mong muốn để tránh sử dụng (++)
, vì với mỗi cuộc gọi reverse(xs)++[x]
danh sách ngày càng lớn hơn (hoặc nhỏ hơn tùy thuộc trên quan điểm. Dù sao, chương trình chỉ đơn giản là phải đi qua khác danh sách với mỗi cuộc gọi)
Ví dụ:
phép nói rằng tôi thực hiện ngược lại theo đề nghị thông qua nối.
reversex ::[Int]->[Int]
reversex [] = []
reversex (x:xs) = reversex(xs)++[x]
Đảo ngược một danh sách [1, 2, 3, 4] sẽ trông hơi như thế này:
reversex [1, 2, 3, 4]
reversex [2, 3, 4] ++ [1]
reversex [3, 4] ++ [2] ++ [1]
reversex [4] ++ [3] ++ [2] ++ [1]
reversex [] ++ [4] ++ [3] ++ [2] ++ [1]
[] ++ [4] ++ [3] ++ [2] ++ [1]
[4] ++ [3] ++ [2] ++ [1]
[4, 3] ++ [2] ++ [1]
[4, 3, 2] ++ [1]
[4, 3, 2, 1]
Tail Recursion sử dụng toán tử khuyết điểm (:) !!!
Một phương pháp để xử lý ngăn xếp cuộc gọi là thêm accumulator. (nó không phải luôn luôn có thể chỉ cần thêm một ắc. Nhưng hầu hết các chức năng đệ quy một giao dịch với rất primitive recursive và do đó có thể được chuyển đổi thành tail recursive functions.)
Với sự giúp đỡ của accumulator nó có thể làm cho ví dụ này hoạt động, sử dụng toán tử cons (:)
. Bộ tích lũy - ys
trong ví dụ của tôi - tích lũy kết quả hiện tại và được truyền đi như một tham số. Do bộ tích lũy, chúng tôi hiện có thể để sử dụng toán tử cons để tích lũy kết quả bằng cách nối thêm đầu danh sách ban đầu của chúng tôi mỗi lần.
reverse' :: (Ord a) => [a] -> [a] -> [a]
reverse' (x:xs) ys = reverse' xs (x:ys)
reverse' [] ys = ys
Có một điều cần lưu ý ở đây.
Bộ tích lũy là một đối số bổ sung. Tôi không biết nếu Haskell cung cấp các thông số mặc định, nhưng trong trường hợp này nó sẽ được tốt đẹp, bởi vì bạn sẽ luôn luôn gọi hàm này với danh sách trống như accumulator như vậy: reverse' [1, 2, 3, 4] []
Có rất nhiều văn học về việc đệ quy đuôi và tôi là chắc chắn có rất nhiều câu hỏi tương tự trên StackExchange/StackOverflow. Hãy sửa tôi nếu bạn tìm thấy bất kỳ sai lầm nào.
Trân trọng!
EDIT 1:
Will Ness chỉ ra một số liên kết câu trả lời cho thực sự tốt đối với những người bạn của những người quan tâm:
EDIT 2:
Ok. Nhờ dFeuer và chỉnh sửa của mình Tôi nghĩ rằng tôi hiểu Haskell tốt hơn một chút.
1.The $!
nằm ngoài hiểu biết của tôi. Trong tất cả các thử nghiệm của tôi, dường như khiến mọi việc trở nên tồi tệ hơn.
2.As dFeuer chỉ ra: Các thunk đại diện cho các ứng dụng của (:)
để x
và y
là ngữ nghĩa giống với x:y
nhưng phải mất nhiều bộ nhớ hơn. Vì vậy, điều này là đặc biệt đối với nhà điều hành cons (và các nhà xây dựng lười biếng) và không cần phải ép buộc mọi thứ theo bất kỳ cách nào.
3. Nếu tôi thay sumUp Số nguyên của một danh sách sử dụng một chức năng rất giống nhau, đánh giá nghiêm ngặt thông qua BangPatterns hoặc seq
chức năng sẽ ngăn chặn ngăn xếp từ phát triển quá lớn nếu được sử dụng một cách thích hợp. ví dụ:
sumUp' :: (Num a, Ord a) => [a] -> a -> a
sumUp' (x:xs) !y = reverse' xs (x + y)
sumUp' [] y = y
Lưu ý trường ở phía trước của y. Tôi đã thử nó trong ghci và nó mất ít bộ nhớ hơn.
Như một lưu ý, bạn có thể (và nên) gọi mà không cần sử dụng dấu ngoặc đơn: 'reverse (x: xs) = reverse xs ++ [x]', hoặc bạn sẽ bị vấp khi bạn làm việc với các hàm có nhiều đối số. –
Không gọi các hàm như 'func (arg)'. Đó là Haskell nghèo. Luôn gọi các hàm như 'func arg'.Mã với không gian rõ ràng làm cho mã tự tin và dễ đọc hơn. – AJFarmar