Lợi ích của chức năng thuần túy


81

Hôm nay tôi đã đọc về chức năng thuần túy, đã nhầm lẫn với việc sử dụng nó:

Một hàm được cho là thuần túy nếu nó trả về cùng một bộ giá trị cho cùng một bộ đầu vào và không có bất kỳ tác dụng phụ nào có thể quan sát được.

Ví dụ: strlen()là một hàm thuần túy trong khi rand()là một hàm không tinh khiết.

__attribute__ ((pure)) int fun(int i)
{
    return i*i;
}

int main()
{
    int i=10;
    printf("%d",fun(i));//outputs 100
    return 0;
}

http://ideone.com/33XJU

Chương trình trên hoạt động theo cách tương tự như trong trường hợp không purekhai báo.

Lợi ích của việc khai báo một hàm là pure[nếu không có thay đổi trong đầu ra] là gì?


7
Có - hãy nhìn vào bản lắp ráp đã tạo.
Philip Kendall

4
Tôi không nghĩ rằng định nghĩa này về độ tinh khiết là đúng - printfví dụ, sẽ đủ điều kiện (gọi nó hai lần với các đối số giống nhau mang lại cùng một giá trị trả về), nhưng nó không thuần khiết.
tdammers

14
@tdammers: Thật vậy, nó thiếu ...and no side-effects...phần.
Frerich Raabe

2
@Ben: entropy đến từ đâu? Ở đây chúng ta đang xử lý các máy xác định (về mặt lý thuyết), cách duy nhất để đưa entropy thực sự vào chúng là từ các nguồn bên ngoài, có nghĩa là các tác dụng phụ. Tất nhiên, chúng tôi có thể cho phép các ngôn ngữ lập trình xác định các hàm không xác định, giả như các tác dụng phụ kỹ thuật không có ở đó và các hàm thực sự là không xác định; nhưng nếu chúng ta làm điều đó, hầu hết các lợi ích thiết thực của việc theo dõi độ tinh khiết sẽ bị mất.
tdammers

3
tdammers là đúng - định nghĩa về nguyên chất được đưa ra ở trên là không chính xác. Thuần túy có nghĩa là đầu ra chỉ phụ thuộc vào các yếu tố đầu vào cho chức năng; ngoài ra, không được có tác dụng phụ có thể quan sát được. "Đầu ra giống nhau cho đầu vào giống nhau" là một bản tóm tắt rất không chính xác về những yêu cầu đó. vi.wikipedia.org/wiki/Pure_
Chức năng

Câu trả lời:


144

pure cho phép trình biên dịch biết rằng nó có thể tạo ra những tối ưu nhất định về chức năng: hãy tưởng tượng một chút mã như

for (int i = 0; i < 1000; i++)
{
    printf("%d", fun(10));
}

Với một chức năng thuần túy, trình biên dịch có thể biết rằng nó cần đánh giá fun(10)một lần và một lần duy nhất, thay vì 1000 lần. Đối với một chức năng phức tạp, đó là một chiến thắng lớn.


Ví dụ, bạn có thể yên tâm sử dụng memoization
Joel Coehoorn

@mob Ý bạn là gì? Tại sao không?
Konrad Rudolph

15
Vì bạn có thể sửa đổi chuỗi (chuỗi ký tự bắt đầu từ một số địa chỉ) mà không sửa đổi đầu vào (con trỏ đến địa chỉ nơi chuỗi bắt đầu), tức là bạn không thể ghi nhớ nó. Nó sẽ chỉ là một hàm thuần túy trong một ngôn ngữ với các chuỗi bất biến (chẳng hạn như Java).
mob

5
@KonradRudolph: Hãy tưởng tượng một chuỗi dài 1000. Hãy gọi strlennó. Sau đó một lần nữa. Điều tương tự có? Bây giờ sửa đổi ký tự thứ hai thành \0. Liệu strlenvẫn quay trở lại năm 1000 bây giờ? Địa chỉ bắt đầu giống nhau (đầu vào == giống nhau) nhưng hàm bây giờ trả về một giá trị khác.
Mike Bailey

5
@mob Đó là một phản đối tốt, rõ ràng là bạn đúng. Tôi đã bị nhầm lẫn bởi thực tế là ngay cả những cuốn sách cũng tuyên bố rằng strlen(trong GCC / glibc) trên thực tế là thuần túy. Nhưng khi nhìn vào việc triển khai glibc cho thấy điều này là sai.
Konrad Rudolph

34

Khi bạn nói một chức năng là 'thuần túy', bạn đang đảm bảo rằng nó không có tác dụng phụ có thể nhìn thấy bên ngoài (và như một nhận xét đã nói, nếu bạn nói dối, điều xấu có thể xảy ra). Biết rằng một hàm là 'thuần túy' có lợi cho trình biên dịch, trình biên dịch có thể sử dụng kiến ​​thức này để thực hiện một số tối ưu hóa nhất định.

Đây là những gì tài liệu GCC nói về purethuộc tính:

nguyên chất

Nhiều hàm không có tác dụng ngoại trừ giá trị trả về và giá trị trả về của chúng chỉ phụ thuộc vào các tham số và / hoặc biến toàn cục. Một hàm như vậy có thể bị loại bỏ biểu thức con phổ biến và tối ưu hóa vòng lặp giống như một toán tử số học. Các hàm này nên được khai báo với thuộc tính pure. Ví dụ,

          int square (int) __attribute__ ((pure));

Câu trả lời của Philip đã cho thấy việc biết một hàm là 'thuần túy' có thể giúp tối ưu hóa vòng lặp như thế nào.

Đây là một để loại bỏ biểu thức con phổ biến (đã cho foolà thuần túy):

a = foo (99) * x + y;
b = foo (99) * x + z;

Có thể trở thành:

_tmp = foo (99) * x;
a = _tmp + y;
b = _tmp + z;

3
Tôi không chắc liệu có làm được điều này hay không, nhưng các hàm thuần túy cũng cho phép trình biên dịch sắp xếp lại thứ tự khi hàm được gọi, nếu việc sắp xếp lại sẽ có lợi. Khi có khả năng xảy ra các tác dụng phụ, trình biên dịch cần phải thận trọng hơn.
mpdonadio

@MPD - Vâng, điều đó nghe có vẻ hợp lý. Và vì một calllệnh là một nút thắt cổ chai cho các CPU siêu cấp, một số trợ giúp từ trình biên dịch có thể giúp ích.
ArjunShankar

Tôi mơ hồ nhớ lại việc sử dụng trình biên dịch DSP cách đây vài năm sẽ sử dụng kỹ thuật này để nhận các giá trị trả về sớm / muộn. Điều này cho phép nó giảm thiểu các gian hàng đường ống.
mpdonadio

1
Có thể tính toán trước "foo (99)" vì 99 là hằng số và foo sẽ luôn trả về cùng một kết quả không? Có thể trong một số loại biên dịch hai giai đoạn?
markwatson

1
@markwatson - Tôi không chắc. Có thể có những trường hợp đơn giản là không thể. ví dụ nếu foolà một phần của đơn vị biên dịch khác (tệp C khác), hoặc trong thư viện được biên dịch trước. Trong cả hai trường hợp, trình biên dịch sẽ không biết những gì foosẽ xảy ra và không thể tính toán trước.
ArjunShankar

28

Ngoài các lợi ích có thể có về thời gian chạy, một chức năng thuần túy sẽ dễ lý giải hơn nhiều khi đọc mã. Hơn nữa, kiểm tra một hàm thuần túy sẽ dễ dàng hơn nhiều vì bạn biết rằng giá trị trả về chỉ phụ thuộc vào giá trị của các tham số.


2
+1, quan điểm của bạn về thử nghiệm là một điều thú vị. Không cần thiết lập và chia nhỏ.
ArjunShankar

15

Một chức năng không thuần túy

int foo(int x, int y) // possible side-effects

giống như một phần mở rộng của một chức năng thuần túy

int bar(int x, int y) // guaranteed no side-effects

trong đó bạn có, ngoài các đối số hàm rõ ràng x, y, phần còn lại của vũ trụ (hoặc bất cứ thứ gì máy tính của bạn có thể giao tiếp) như một đầu vào tiềm năng ngầm định. Tương tự như vậy, bên cạnh giá trị trả về số nguyên rõ ràng, bất cứ thứ gì máy tính của bạn có thể ghi vào đều là một phần của giá trị trả về.

Cần phải rõ ràng tại sao lý luận về một chức năng thuần túy dễ dàng hơn nhiều so với một chức năng không thuần túy.


1
+1: Sử dụng vũ trụ như một đầu vào tiềm năng là một cách rất hay để giải thích sự khác biệt giữa tinh khiết và không tinh khiết.
ArjunShankar

thực sự, đây là ý tưởng đằng sau monads.
Kristopher Micinski

7

Chỉ là một tiện ích bổ sung, tôi muốn đề cập rằng C ++ 11 hệ thống hóa mọi thứ bằng cách sử dụng từ khóa constexpr. Thí dụ:

#include <iostream>
#include <cstring>

constexpr unsigned static_strlen(const char * str, unsigned offset = 0) {
        return (*str == '\0') ? offset : static_strlen(str + 1, offset + 1);
}

constexpr const char * str = "asdfjkl;";

constexpr unsigned len = static_strlen(str); //MUST be evaluated at compile time
//so, for example, this: int arr[len]; is legal, as len is a constant.

int main() {
    std::cout << len << std::endl << std::strlen(str) << std::endl;
    return 0;
}

Những hạn chế trong việc sử dụng constexpr làm cho nó trở nên thuần túy nhất. Bằng cách này, trình biên dịch có thể tối ưu hóa mạnh mẽ hơn (chỉ cần đảm bảo rằng bạn sử dụng đệ quy đuôi, vui lòng!) Và đánh giá hàm tại thời điểm biên dịch thay vì thời gian chạy.

Vì vậy, để trả lời câu hỏi của bạn, là nếu bạn đang sử dụng C ++ (tôi biết bạn đã nói C, nhưng chúng có liên quan với nhau), việc viết một hàm thuần túy theo đúng phong cách cho phép trình biên dịch thực hiện tất cả các loại điều thú vị với hàm: -)


4

Nói chung, các hàm Pure có 3 ưu điểm so với các hàm không tinh khiết mà trình biên dịch có thể tận dụng:

Bộ nhớ đệm

Giả sử rằng bạn có hàm thuần túy fđang được gọi 100000 lần, vì nó có tính xác định và chỉ phụ thuộc vào các tham số của nó, trình biên dịch có thể tính toán giá trị của nó một lần và sử dụng nó khi cần thiết

Song song

Các hàm thuần túy không đọc hoặc ghi vào bất kỳ bộ nhớ dùng chung nào và do đó có thể chạy trong các chuỗi riêng biệt mà không có bất kỳ hậu quả bất ngờ nào

Chuyển qua tài liệu tham khảo

Một hàm f(struct t)nhận đối số của nó ttheo giá trị và mặt khác, trình biên dịch có thể chuyển tbằng tham chiếu đến fnếu nó được khai báo là thuần túy trong khi đảm bảo rằng giá trị của tsẽ không thay đổi và có hiệu suất tăng


Ngoài việc cân nhắc về thời gian biên dịch, các hàm thuần túy có thể được kiểm tra khá dễ dàng: chỉ cần gọi chúng.

Không cần phải xây dựng các đối tượng hoặc các kết nối giả đến DBs / hệ thống tệp.

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.