Tôi vừa tìm hiểu cách đánh giá lười biếng hoạt động và tôi đã tự hỏi: tại sao đánh giá lười biếng không được áp dụng trong mọi phần mềm hiện được sản xuất? Tại sao vẫn sử dụng đánh giá háo hức?
Tôi vừa tìm hiểu cách đánh giá lười biếng hoạt động và tôi đã tự hỏi: tại sao đánh giá lười biếng không được áp dụng trong mọi phần mềm hiện được sản xuất? Tại sao vẫn sử dụng đánh giá háo hức?
Câu trả lời:
Đánh giá lười biếng đòi hỏi phải giữ sổ sách - bạn phải biết nếu nó được đánh giá chưa và những thứ như vậy. Đánh giá háo hức luôn được đánh giá, vì vậy bạn không cần phải biết. Điều này đặc biệt đúng trong bối cảnh đồng thời.
Thứ hai, đó là tầm thường để chuyển đổi đánh giá háo hức vào đánh giá lười biếng bằng cách đóng gói nó vào một đối tượng hàm được gọi sau đó, nếu bạn muốn.
Thứ ba, đánh giá lười biếng hàm ý mất kiểm soát. Điều gì sẽ xảy ra nếu tôi lười biếng đánh giá việc đọc một tập tin từ đĩa? Hoặc nhận được thời gian? Điều đó không được chấp nhận.
Đánh giá háo hức có thể hiệu quả hơn và dễ kiểm soát hơn, và được chuyển đổi tầm thường thành đánh giá lười biếng. Tại sao bạn muốn đánh giá lười biếng?
readFile
là những gì tôi cần. Bên cạnh đó, chuyển đổi từ đánh giá lười biếng sang háo hức cũng chỉ là chuyện nhỏ.
head [1 ..]
mang lại cho bạn một ngôn ngữ thuần túy được đánh giá một cách háo hức, bởi vì trong Haskell nó mang lại 1
?
Chủ yếu là vì mã lười và trạng thái có thể trộn lẫn xấu và gây ra một số lỗi khó tìm. Nếu trạng thái của một đối tượng phụ thuộc thay đổi, giá trị của đối tượng lười biếng của bạn có thể sai khi được đánh giá. Sẽ tốt hơn nhiều nếu lập trình viên mã hóa rõ ràng đối tượng để lười biếng khi anh ấy / cô ấy biết tình huống này là phù hợp.
Mặt khác, Haskell sử dụng đánh giá Lazy cho mọi thứ. Điều này là có thể bởi vì đó là ngôn ngữ chức năng và không sử dụng trạng thái (ngoại trừ trong một vài trường hợp đặc biệt khi chúng được đánh dấu rõ ràng)
set!
trình thông dịch Scheme lười biếng. > :(
Đánh giá lười biếng không phải lúc nào cũng tốt hơn.
Lợi ích về hiệu suất của đánh giá lười biếng có thể rất lớn, nhưng không khó để tránh hầu hết các đánh giá không cần thiết trong môi trường háo hức - chắc chắn lười biếng làm cho nó dễ dàng và đầy đủ, nhưng hiếm khi đánh giá không cần thiết trong mã là một vấn đề lớn.
Điều tốt về đánh giá lười biếng là khi nó cho phép bạn viết mã rõ ràng hơn; lấy số nguyên tố thứ 10 bằng cách lọc danh sách số tự nhiên vô hạn và lấy phần tử thứ 10 của danh sách đó là một trong những cách tiến hành ngắn gọn và rõ ràng nhất: (mã giả)
let numbers = [1,2...]
fun is_prime x = none (map (y-> x mod y == 0) [2..x-1])
let primes = filter is_prime numbers
let tenth_prime = first (take primes 10)
Tôi tin rằng sẽ rất khó để diễn đạt mọi thứ một cách chính xác mà không lười biếng.
Nhưng sự lười biếng không phải là câu trả lời cho mọi thứ. Đối với người mới bắt đầu, sự lười biếng không thể được áp dụng một cách minh bạch khi có trạng thái và tôi tin rằng trạng thái không thể được tự động phát hiện (trừ khi bạn đang làm việc nói, Haskell, khi trạng thái khá rõ ràng). Vì vậy, trong hầu hết các ngôn ngữ, sự lười biếng cần phải được thực hiện thủ công, điều này làm cho mọi thứ không rõ ràng và do đó loại bỏ một trong những lợi ích lớn của việc lười biếng.
Hơn nữa, sự lười biếng có những hạn chế về hiệu suất, vì nó phải chịu một chi phí đáng kể trong việc giữ các biểu thức không được đánh giá xung quanh; họ sử dụng hết dung lượng và họ làm việc chậm hơn so với các giá trị đơn giản. Không có gì lạ khi phát hiện ra rằng bạn phải háo hức mã vì phiên bản lười biếng là chó chậm - và đôi khi rất khó để lý giải về hiệu suất.
Vì nó có xu hướng xảy ra, không có chiến lược tốt nhất tuyệt đối. Lười là tuyệt vời nếu bạn có thể viết mã tốt hơn bằng cách tận dụng các cấu trúc dữ liệu vô hạn hoặc các chiến lược khác mà nó cho phép bạn sử dụng, nhưng háo hức có thể dễ dàng tối ưu hóa hơn.
Dưới đây là một so sánh ngắn về ưu và nhược điểm của đánh giá háo hức và lười biếng:
Đánh giá háo hức:
Chi phí tiềm năng của công cụ đánh giá không cần thiết.
Vô tư, đánh giá nhanh.
Đánh giá lười biếng:
Không có đánh giá không cần thiết.
Sổ sách kế toán ở mỗi lần sử dụng một giá trị.
Vì vậy, nếu bạn có nhiều biểu hiện không bao giờ phải đánh giá, lười biếng là tốt hơn; Tuy nhiên, nếu bạn không bao giờ có một biểu thức không cần phải đánh giá, thì lười biếng hoàn toàn là chi phí.
Bây giờ, hãy xem phần mềm trong thế giới thực: Có bao nhiêu chức năng mà bạn viết không yêu cầu đánh giá tất cả các đối số của chúng? Đặc biệt với các hàm ngắn hiện đại chỉ làm một việc, tỷ lệ các hàm rơi vào loại này rất thấp. Do đó, đánh giá lười biếng sẽ chỉ giới thiệu phần lớn sổ sách kế toán, mà không có cơ hội thực sự tiết kiệm bất cứ điều gì.
Do đó, đánh giá lười biếng đơn giản là không trả tiền trung bình, đánh giá háo hức là phù hợp hơn cho mã hiện đại.
Như @DeadMG lưu ý Đánh giá lười biếng đòi hỏi phải giữ sổ sách. Điều này có thể tốn kém liên quan đến đánh giá háo hức. Hãy xem xét tuyên bố này:
i = (243 * 414 + 6562 / 435.0 ) ^ 0.5 ** 3
Điều này sẽ mất một chút tính toán để tính toán. Nếu tôi sử dụng đánh giá lười biếng, thì tôi cần kiểm tra xem nó có được đánh giá mỗi lần tôi sử dụng không. Nếu đây là một vòng lặp được sử dụng nhiều thì chi phí tăng lên đáng kể, nhưng không có lợi ích gì.
Với đánh giá háo hức và một trình biên dịch đàng hoàng, công thức được tính toán tại thời điểm biên dịch. Hầu hết các trình tối ưu hóa sẽ chuyển nhiệm vụ ra khỏi bất kỳ vòng lặp nào xảy ra nếu thích hợp.
Đánh giá lười biếng là phù hợp nhất để tải dữ liệu sẽ được truy cập không thường xuyên và có chi phí cao để truy xuất. Do đó, nó thích hợp hơn cho các trường hợp cạnh hơn chức năng cốt lõi.
Nói chung, đó là cách thực hành tốt để đánh giá những thứ thường xuyên được truy cập càng sớm càng tốt. Đánh giá lười biếng không hoạt động với thực hành này. Nếu bạn sẽ luôn truy cập một cái gì đó, tất cả các đánh giá lười biếng sẽ làm là thêm chi phí. Chi phí / lợi ích của việc sử dụng đánh giá lười biếng giảm khi mục được truy cập trở nên ít có khả năng được truy cập hơn.
Luôn luôn sử dụng đánh giá lười biếng cũng ngụ ý tối ưu hóa sớm. Đây là một thực tiễn tồi tệ thường dẫn đến mã phức tạp và tốn kém hơn nhiều có thể là trường hợp khác. Thật không may, tối ưu hóa sớm thường dẫn đến mã thực hiện chậm hơn mã đơn giản hơn. Cho đến khi bạn có thể đo lường hiệu quả của việc tối ưu hóa, việc tối ưu hóa mã của bạn là một ý tưởng tồi.
Tránh tối ưu hóa sớm không xung đột với thực tiễn mã hóa tốt. Nếu các thực tiễn tốt không được áp dụng, tối ưu hóa ban đầu có thể bao gồm áp dụng các thực tiễn mã hóa tốt như di chuyển các tính toán ra khỏi các vòng lặp.
Nếu chúng ta có khả năng phải đánh giá đầy đủ một biểu thức để xác định giá trị của nó thì đánh giá lười biếng có thể là một bất lợi. Giả sử chúng tôi có một danh sách dài các giá trị boolean và chúng tôi muốn tìm hiểu xem tất cả chúng có đúng không:
[True, True, True, ... False]
Để làm điều này, chúng tôi phải xem xét mọi yếu tố trong danh sách, không có vấn đề gì, vì vậy không có khả năng lười biếng cắt đứt đánh giá. Chúng ta có thể sử dụng một nếp gấp để xác định xem tất cả các giá trị boolean trong danh sách có đúng không. Nếu chúng ta sử dụng quyền gấp, sử dụng đánh giá lười biếng, chúng ta sẽ không nhận được bất kỳ lợi ích nào của việc đánh giá lười biếng vì chúng ta phải xem xét mọi yếu tố trong danh sách:
foldr (&&) True [True, True, True, ... False]
> 0.27 secs
Một nếp gấp bên phải sẽ chậm hơn nhiều trong trường hợp này so với nếp gấp nghiêm ngặt bên trái, không sử dụng đánh giá lười biếng:
foldl' (&&) True [True, True, True, ... False]
> 0.09 secs
Lý do là một nếp gấp nghiêm ngặt sử dụng đệ quy đuôi, có nghĩa là nó tích lũy giá trị trả về và không tích tụ và lưu trữ trong bộ nhớ một chuỗi hoạt động lớn. Điều này nhanh hơn nhiều so với quyền gấp bởi vì cả hai chức năng đều phải xem toàn bộ danh sách và quyền gấp không thể sử dụng đệ quy đuôi. Vì vậy, vấn đề là, bạn nên sử dụng bất cứ điều gì tốt nhất cho nhiệm vụ trong tay.