Tại sao việc sử dụng toán tử gán hoặc vòng lặp không được khuyến khích trong lập trình chức năng?


9

Nếu hàm của tôi đáp ứng hai yêu cầu dưới đây, tôi tin rằng hàm Sum trả về tổng của các mục trong danh sách trong đó mục được đánh giá là đúng với điều kiện đã cho đủ điều kiện để được gọi là hàm thuần túy, phải không?

1) Với tập hợp i / p đã cho, cùng một o / p được trả về bất kể thời gian khi hàm được gọi

2) Nó không có tác dụng phụ

public int Sum(Func<int,bool> predicate, IEnumerable<int> numbers){
    int result = 0;
    foreach(var item in numbers)
        if(predicate(item)) result += item;
    return result;
}

Thí dụ : Sum(x=>x%2==0, new List<int> {1,2,3,4,5...100});

Lý do tôi hỏi câu hỏi này là vì tôi thấy hầu hết mọi nơi mọi người khuyên nên tránh toán tử gán và vòng lặp vì đó là phong cách lập trình bắt buộc. Vì vậy, những gì có thể đi sai với ví dụ trên mà sử dụng các vòng lặp và toán tử gán trong bối cảnh lập trình hàm?


1
Nó không có bất kỳ tác dụng phụ nào - nó có tác dụng phụ, khi itembiến bị đột biến trong vòng lặp.
Fabio

@Fabio ok. Nhưng bạn có thể giải thích về phạm vi tác dụng phụ?
rahulaga_dev

Câu trả lời:


16

Điều gì trong lập trình chức năng tạo nên sự khác biệt?

Lập trình chức năng là theo nguyên tắc khai báo . Bạn nói kết quả của bạn là thay vì cách tính toán nó.

Chúng ta hãy xem thực hiện chức năng thực sự của đoạn trích của bạn. Trong Haskell, nó sẽ là:

predsum pred numbers = sum (filter pred numbers)

Là nó rõ ràng những gì kết quả là? Khá là như vậy, nó là tổng của các số đáp ứng vị ngữ. được tính như thế nào? Tôi không quan tâm, hãy hỏi trình biên dịch.

Bạn có thể nói rằng sử dụng sumfilterlà một mẹo và nó không được tính. Hãy thực hiện nó mà không có những người trợ giúp này sau đó (mặc dù cách tốt nhất sẽ là thực hiện chúng trước).

Giải pháp "Lập trình chức năng 101" không sử dụng sumlà với đệ quy:

sum pred list = 
    case list of
        [] -> 0
        h:t -> if pred h then h + sum pred t
                         else sum pred t

Nó vẫn còn khá rõ ràng những gì kết quả về cuộc gọi chức năng duy nhất là. Nó là 0, hoặc recursive call + h or 0, tùy thuộc vào pred h. Vẫn còn khá căng thẳng, ngay cả khi kết quả cuối cùng không rõ ràng ngay lập tức (mặc dù với một chút thực hành, điều này thực sự đọc giống như một forvòng lặp).

So sánh với phiên bản của bạn:

public int Sum(Func<int,bool> predicate, IEnumerable<int> numbers){
    int result = 0;
    foreach(var item in numbers)
        if (predicate(item)) result += item;
    return result;
}

Kết quả là gì? Ồ, tôi thấy: returntuyên bố duy nhất , không có gì ngạc nhiên ở đây : return result.

Nhưng là resultgì? int result = 0? Có vẻ không đúng. Bạn làm một cái gì đó sau đó với điều đó 0. Ok, bạn thêm items vào nó. Và như thế.

Tất nhiên, đối với hầu hết các lập trình viên, điều này khá rõ ràng những gì xảy ra trong một funciton đơn giản như thế này, nhưng thêm một số returncâu lệnh bổ sung hoặc như vậy và nó đột nhiên trở nên khó theo dõi hơn. Tất cả các mã là về làm thế nào , và những gì còn lại để người đọc tìm ra - đây rõ ràng là một phong cách rất bắt buộc .

Vì vậy, các biến và vòng lặp sai?

Không.

Có nhiều điều dễ giải thích hơn nhiều bởi chúng, và nhiều thuật toán yêu cầu trạng thái có thể thay đổi phải nhanh. Nhưng các biến vốn là bắt buộc, giải thích làm thế nào thay vì cái gì và đưa ra dự đoán nhỏ về giá trị của chúng có thể là một vài dòng sau hoặc sau một vài lần lặp. Các vòng lặp thường yêu cầu trạng thái có ý nghĩa, và do đó chúng cũng bắt buộc.

Các biến và vòng lặp đơn giản là không lập trình chức năng.

Tóm lược

Lập trình funcitonal đương thời là một chút phong cách và một cách suy nghĩ hữu ích hơn một mô hình. Sở thích mạnh mẽ cho các chức năng thuần túy là trong suy nghĩ này, nhưng thực tế nó chỉ là một phần nhỏ.

Hầu hết các ngôn ngữ phổ biến cho phép bạn sử dụng một số cấu trúc chức năng. Ví dụ: trong Python bạn có thể chọn giữa:

result = 0
for num in numbers:
    if pred(result):
        result += num
return result

hoặc là

return sum(filter(pred, numbers))

hoặc là

return sum(n for n in numbers if pred(n))

Các biểu thức chức năng này phù hợp độc đáo cho các vấn đề loại đó và chỉ cần làm cho mã ngắn hơn (và ngắn hơn là tốt ). Bạn không nên thay thế mã bắt buộc bằng chúng, nhưng khi chúng phù hợp, chúng hầu như luôn là lựa chọn tốt hơn.


thx cho lời giải thích tốt đẹp !!
rahulaga_dev

1
@RahulAgarwal Bạn có thể thấy câu trả lời này thú vị, nó độc đáo nắm bắt một khái niệm tiếp tuyến về việc thiết lập sự thật so với mô tả các bước. Tôi cũng thích cụm từ "ngôn ngữ khai báo chứa tác dụng phụ trong khi ngôn ngữ mệnh lệnh thì không" - thông thường các chương trình chức năng có các vết cắt rõ ràng và rất rõ ràng giữa mã trạng thái xử lý với thế giới bên ngoài (hoặc thực hiện một số thuật toán tối ưu hóa) và mã chức năng thuần túy.
Frax

1
@Frax: cảm ơn !! Tôi sẽ nhìn vào nó. Gần đây cũng bắt gặp Rich Hickey nói về Giá trị của các giá trị Nó thực sự tuyệt vời. Tôi nghĩ quy tắc một ngón tay cái - "làm việc với các giá trị và biểu thức thay vì làm việc với thứ gì đó giữ giá trị và có thể thay đổi"
rahulaga_dev

1
@Frax: Cũng thật công bằng khi nói rằng FP là một sự trừu tượng hóa đối với lập trình bắt buộc - bởi vì cuối cùng ai đó phải hướng dẫn máy về "cách làm", phải không? Nếu có, thì không phải lập trình bắt buộc có kiểm soát mức độ thấp hơn so với FP?
rahulaga_dev

1
@Frax: Tôi đồng ý với Rahul rằng mệnh lệnh là cấp thấp hơn theo nghĩa là nó gần với máy bên dưới hơn. Nếu phần cứng có thể tạo bản sao dữ liệu miễn phí, chúng tôi sẽ không cần cập nhật phá hủy để cải thiện hiệu quả. Theo nghĩa này, mô hình mệnh lệnh gần với kim loại hơn.
Giorgio

9

Việc sử dụng trạng thái đột biến thường không được khuyến khích trong lập trình chức năng. Các vòng lặp không được khuyến khích do hậu quả, bởi vì các vòng lặp chỉ hữu ích khi kết hợp với trạng thái đột biến.

Toàn bộ chức năng là thuần túy, điều này thật tuyệt vời, nhưng mô hình của lập trình chức năng không chỉ áp dụng ở cấp độ của toàn bộ chức năng. Bạn cũng muốn tránh trạng thái đột biến cũng ở cấp độ địa phương, bên trong các chức năng. Và lý do về cơ bản là giống nhau: Tránh trạng thái có thể thay đổi làm cho mã dễ hiểu hơn và ngăn ngừa một số lỗi nhất định.

Trong trường hợp của bạn, bạn có thể viết numbers.Where(predicate).Sum()rõ ràng đơn giản hơn nhiều. Và đơn giản hơn có nghĩa là ít lỗi hơn.


cám ơn !! Tôi nghĩ rằng tôi đã bỏ lỡ dòng nổi bật - nhưng mô hình lập trình chức năng không chỉ áp dụng ở cấp độ của toàn bộ chức năng Nhưng bây giờ tôi cũng đang tự hỏi làm thế nào để hình dung ranh giới này. Về cơ bản từ góc độ người tiêu dùng, đó là chức năng thuần túy nhưng nhà phát triển thực sự đã viết chức năng này đã không tuân theo các nguyên tắc chức năng thuần túy? bối rối :(
rahulaga_dev

@RahulAgarwal: Ranh giới nào?
JacquesB

Tôi bối rối trong ý nghĩa nếu mô hình lập trình đủ điều kiện để trở thành FP từ quan điểm của người tiêu dùng chức năng? Bcoz nếu tôi nhìn vào việc thực hiện triển khai Wheretrong numbers.Where(predicate).Sum()- nó sử dụng foreachvòng lặp.
rahulaga_dev

3
@RahulAgarwal: Là người tiêu dùng của một chức năng, bạn không thực sự quan tâm nếu một chức năng hoặc mô-đun bên trong sử dụng trạng thái có thể thay đổi miễn là nó hoàn toàn bên ngoài.
JacquesB

7

Mặc dù bạn đúng rằng theo quan điểm của người quan sát bên ngoài, Sumchức năng của bạn là thuần túy, việc triển khai bên trong rõ ràng không thuần túy - bạn có trạng thái được lưu trữ trong resultđó bạn liên tục thay đổi. Một trong những lý do để tránh trạng thái đột biến là vì nó tạo ra tải nhận thức lớn hơn cho lập trình viên, từ đó dẫn đến nhiều lỗi hơn [cần dẫn nguồn] .

Mặc dù trong một ví dụ đơn giản như thế này, lượng trạng thái có thể thay đổi được lưu trữ có thể đủ nhỏ để nó không gây ra bất kỳ vấn đề nghiêm trọng nào, nhưng nguyên tắc chung vẫn được áp dụng. Một ví dụ đồ chơi như Sumcó lẽ không phải là cách tốt nhất để minh họa lợi thế của lập trình chức năng so với mệnh lệnh - hãy thử làm một cái gì đó với nhiều trạng thái có thể thay đổi và những lợi thế có thể trở nên rõ ràng hơn.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.