Lập trình chức năng cho hiệu ứng phụ vòng lặp


8

Tôi đang cố gắng tìm hiểu xem tại sao có một biến cục bộ hoặc một vòng lặp for bên trong một hàm không được coi là lập trình hàm thuần túy.

Cho hàm này:

int as_int(char *str)
{
    int acc; /* accumulate the partial result */

    for (acc = 0; isdigit(*str); str++) {
        acc = acc * 10 + (*str - '0');
    }

    return acc;
}

Trong trường hợp nào thì acc biến sẽ là tác dụng phụ? Ngay cả trong một môi trường đồng thời, mỗi lần gọi hàm sẽ có bản sao acc riêng. Vì vậy, tôi không hiểu tại sao nó không được phép trong lập trình chức năng.



5
as_intlà một hàm thuần túy, nhưng mã bên trong nó không thuần túy.
Doval

1
Mã thuần thường được liên kết với các biến không thay đổi. acclà đột biến.
Florian Margaine

Câu trả lời:


11

Vòng lặp trong lập trình chức năng không được thực hiện với báo cáo kiểm soát như forwhile, nó được thực hiện với các cuộc gọi rõ ràng để các chức năng như map, foldhoặc đệ quy - tất cả đều có liên quan đến việc đặt vòng lặp gọi nội bên trong chức năng khác . Nếu mã vòng lặp làm thay đổi các biến bên ngoài vòng lặp, hàm vòng lặp bên trong này sẽ thao tác các biến bên ngoài phạm vi của nó và do đó sẽ không trong sạch . Vì vậy, toàn bộ chức năng bên ngoài là thuần túy, nhưng vòng lặp thì không. Cấu trúc vòng lặp trong lập trình chức năng yêu cầu bạn làm cho trạng thái rõ ràng. Dịch mã của bạn sang một cái gì đó bằng cách sử dụng các công cụ lặp lập trình chức năng cho thấy tạp chất:

int as_int(char *str)
{
    int acc = 0; /* accumulate the partial result */

    map(takeWhile(isdigit, str), void function(char *chr) {
      acc = acc * 10 + (chr - '0');
    });

    return acc;
}

(Lưu ý - cú pháp này là gần đúng để có được ý tưởng chung)

Mã này sử dụng một hàm bên trong cho thân vòng lặp phải biến đổi biến accnằm ngoài phạm vi của nó. Điều này là không tinh khiết - chức năng vòng lặp bên trong phụ thuộc vào bối cảnh vòng lặp bên ngoài , gọi nó nhiều lần với cùng một ký tự sẽ có tác dụng phụ và thứ tự bạn gọi nó trong chuỗi các ký tự là vấn đề. Trong lập trình chức năng, để biến điều này thành một hàm thuần túy, bạn sẽ phải làm cho sự phụ thuộc này vào trạng thái được chuyển giữa các lần lặp lặp rõ ràng với fold:

int as_int(char *str)
{
    return fold(takeWhile(isdigit, str), 0, int function(char *chr, int acc) {
      return acc * 10 + (chr - '0');
    });
}

foldsử dụng chức năng của hai đối số cho thân vòng lặp bên trong: đối số thứ nhất là một mục trong chuỗi foldđược lặp lại, trong khi đối số thứ hai là một giá trị mà thân vòng lặp bên trong sử dụng để xây dựng kết quả một phần. Đối với lần lặp vòng lặp thứ nhất, acclà 0, đối với lần thứ hai, acclà bất cứ điều gì mà lệnh gọi hàm vòng lặp thứ nhất được trả về, đối với vòng thứ ba, đó là bất cứ vòng lặp thứ hai nào được trả về và vòng lặp cuối cùng trả về kết quả của toàn bộ foldbiểu thức.

Lưu ý rằng đây thực sự không phải là vấn đề với mã của bạn từ góc độ của phần còn lại của chương trình - cả hai định nghĩa as_intđều thuần túy. Sự khác biệt là bằng cách biến mã vòng lặp bên trong thành một hàm thuần túy, bạn có thể tận dụng hàng loạt công cụ mà lập trình chức năng cung cấp để phân tách vòng lặp thành một thứ gì đó mang tính khai báo hơn (ví dụ: sử dụng TakeWhile, gập, lọc, ánh xạ, v.v.)


6
+1. Trong số những lợi thế của việc sử dụng chức năng đặt hàng cao như takeWhile, fold, filter, map, (tức là khai báo kiểu) là bạn cũng dừng suy nghĩ về "Tính một cái gì đó bởi vị trí bộ nhớ cập nhật triệt tiêu". Theo cách này, kết quả không phụ thuộc vào lịch sử / trình tự chính xác của các bước tính toán.
Giorgio
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.