Tính không thay đổi hoặc tính đột biến không phải là một khái niệm có ý nghĩa trong lập trình chức năng.
Bối cảnh tính toán
Đây là một câu hỏi rất hay là một câu hỏi thú vị (không phải là một bản sao) cho một câu hỏi gần đây khác: sự khác biệt giữa sự phân công, định giá và ràng buộc tên là gì?
Thay vì trả lời từng câu một của bạn, tôi đang cố gắng cung cấp cho bạn một cái nhìn tổng quan có cấu trúc về những gì đang diễn ra.
Có một số vấn đề được xem xét để trả lời bạn, bao gồm:
Phong cách lập trình chức năng có vẻ ngớ ngẩn vì bạn nhìn thấy nó với con mắt lập trình mệnh lệnh. Nhưng đó là một mô hình khác nhau, và các khái niệm và nhận thức cấp bách của bạn là xa lạ, không đúng chỗ. Các trình biên dịch không có định kiến như vậy.
Nhưng kết luận cuối cùng là có thể viết các chương trình theo cách hoàn toàn chức năng, bao gồm cả học máy, lập trình chức năng nghĩ không có khái niệm lưu trữ dữ liệu. Tôi dường như không đồng ý ở điểm này với các câu trả lời khác.
Với hy vọng một số ít sẽ được quan tâm mặc dù độ dài của câu trả lời này.
Mô hình tính toán
Câu hỏi là về lập trình chức năng (hay còn gọi là lập trình ứng dụng), một mô hình tính toán cụ thể, có đại diện lý thuyết và đơn giản nhất là phép tính lambda.
Nếu bạn ở mức lý thuyết, có nhiều mô hình tính toán: máy Turing (TM), máy RAM và các loại khác , máy tính lambda, logic tổ hợp, lý thuyết hàm đệ quy, hệ thống bán phần, v.v. các mô hình đã được chứng minh tương đương về những gì họ có thể giải quyết, và đó là ý chính của
luận án Church-Turing .
Một khái niệm quan trọng là giảm các mô hình cho nhau, là cơ sở để thiết lập các tương đương dẫn đến luận điểm Church-Turing. Nhìn từ góc độ lập trình viên, việc giảm mô hình này sang mô hình khác là khá nhiều thứ thường được gọi là trình biên dịch. Nếu bạn lấy lập trình logic làm mô hình tính toán, nó hoàn toàn khác với mô hình do PC bạn mua trong cửa hàng cung cấp và trình biên dịch sẽ dịch các chương trình được viết bằng ngôn ngữ lập trình logic sang mô hình tính toán được trình bày bởi PC của bạn (khá nhiều máy tính RAM).
β
Trong thực tế, các ngôn ngữ lập trình mà chúng ta sử dụng có xu hướng trộn các khái niệm từ các nguồn gốc lý thuyết khác nhau, cố gắng làm điều đó để các phần được chọn của chương trình có thể được hưởng lợi từ các thuộc tính của mô hình nào đó khi thích hợp. Tương tự, mọi người xây dựng hệ thống có thể chọn các ngôn ngữ khác nhau cho các thành phần khác nhau, để phù hợp nhất với ngôn ngữ với nhiệm vụ trong tay.
Do đó, bạn hiếm khi thấy một mô hình lập trình ở trạng thái thuần túy trong ngôn ngữ lập trình. Các ngôn ngữ lập trình vẫn được phân loại theo mô hình chi phối, nhưng các thuộc tính của ngôn ngữ có thể bị ảnh hưởng khi các khái niệm từ các mô hình khác có liên quan, thường làm mờ sự khác biệt và các vấn đề khái niệm.
Thông thường, các ngôn ngữ như Haskell và ML hoặc CAML được coi là chức năng, nhưng chúng có thể cho phép hành vi bắt buộc ... Khác tại sao người ta lại nói về " tập hợp con hoàn toàn chức năng "?
Sau đó, người ta có thể khẳng định rằng, bạn có thể làm điều này hoặc bằng ngôn ngữ lập trình chức năng của tôi, nhưng nó không thực sự trả lời một câu hỏi về lập trình chức năng khi nó dựa vào những gì có thể được coi là chức năng bổ sung.
Các câu trả lời nên liên quan chính xác hơn đến một mô hình cụ thể, không có phần bổ sung.
Một biến là gì?
Một vấn đề khác là việc sử dụng thuật ngữ. Trong toán học, một biến là một thực thể đại diện cho một giá trị không xác định trong một số miền. Nó được sử dụng cho các mục đích khác nhau. Được sử dụng trong một phương trình, nó có thể đại diện cho bất kỳ giá trị nào sao cho phương trình được xác minh. Tầm nhìn này được sử dụng trong lập trình logic dưới tên " biến logic ", có thể là do biến tên đã có ý nghĩa khác khi lập trình logic được phát triển.
Trong lập trình mệnh lệnh truyền thống, một biến được hiểu là một loại vật chứa (hoặc vị trí bộ nhớ) có thể ghi nhớ biểu diễn của một giá trị và có thể thay thế giá trị hiện tại của nó bằng một loại khác).
Trong lập trình hàm, một biến có cùng mục đích trong toán học với tư cách là người giữ chỗ cho một số giá trị, chưa được cung cấp. Trong lập trình mệnh lệnh truyền thống, vai trò này thực sự được chơi bằng hằng số (không bị nhầm lẫn với nghĩa đen được xác định giá trị được biểu thị bằng một ký hiệu cụ thể cho miền giá trị đó, chẳng hạn như 123, true, ["abdcz", 3.14]).
Các biến thuộc bất kỳ loại nào, cũng như hằng số, có thể được biểu thị bằng các định danh.
Biến mệnh lệnh có thể thay đổi giá trị của nó và đó là cơ sở cho tính đột biến. Các biến chức năng không thể.
Ngôn ngữ lập trình thường cho phép xây dựng các thực thể lớn hơn từ các thực thể nhỏ hơn trong ngôn ngữ.
Các ngôn ngữ bắt buộc cho phép các cấu trúc như vậy bao gồm các biến và đó là những gì mang lại cho bạn dữ liệu có thể thay đổi.
Cách đọc chương trình
Một chương trình về cơ bản là một mô tả trừu tượng về thuật toán của bạn là một số ngôn ngữ, cho dù là một thiết kế thực dụng hay một ngôn ngữ thuần túy theo mô hình.
Về nguyên tắc, bạn có thể lấy mọi tuyên bố cho những gì nó được cho là có nghĩa trừu tượng. Sau đó, trình biên dịch sẽ dịch nó sang một số dạng thích hợp để máy tính thực thi, nhưng đó không phải là vấn đề của bạn trong phép tính gần đúng đầu tiên.
Tất nhiên, thực tế thì khắc nghiệt hơn một chút và thường có ý tưởng tốt về những gì xảy ra để tránh các cấu trúc mà trình biên dịch sẽ không biết cách xử lý để thực thi hiệu quả. Nhưng đó đã là tối ưu hóa ... trình biên dịch nào có thể rất tốt, thường tốt hơn so với lập trình viên.
Lập trình chức năng và khả năng biến đổi
Tính tương tác dựa trên sự tồn tại của các biến bắt buộc có thể chứa các giá trị, được thay đổi bằng cách gán. Vì những thứ này không tồn tại trong lập trình chức năng, mọi thứ có thể được xem là bất biến.
Lập trình giao dịch độc quyền với các giá trị.
Bốn tuyên bố đầu tiên của bạn về tính bất biến hầu hết là chính xác, nhưng mô tả với quan điểm bắt buộc một cái gì đó không bắt buộc. Nó hơi giống như mô tả với màu sắc trong một thế giới mà mọi người đều bị mù. Bạn đang sử dụng các khái niệm xa lạ với lập trình chức năng.
Bạn chỉ có các giá trị thuần túy và một mảng các số nguyên là một giá trị thuần túy. Để có được một mảng khác chỉ khác nhau trong một phần tử, bạn phải sử dụng một giá trị mảng khác. Thay đổi một yếu tố chỉ là một khái niệm không tồn tại trong bối cảnh này. Bạn có thể có một hàm có một mảng và một số chỉ mục làm đối số và trả về một kết quả là một mảng gần như giống hệt nhau chỉ khác nhau khi được chỉ định bởi các chỉ mục. Nhưng nó vẫn là một giá trị mảng độc lập. Làm thế nào những giá trị này được đại diện không phải là vấn đề của bạn. Có thể họ "chia sẻ" rất nhiều trong bản dịch bắt buộc cho máy tính ... nhưng đó là công việc của nhà soạn nhạc ... và bạn thậm chí không muốn biết nó đang biên dịch loại kiến trúc máy nào.
Bạn không sao chép các giá trị (nó không có ý nghĩa, đó là một khái niệm xa lạ). Bạn chỉ cần sử dụng các giá trị tồn tại trong các miền bạn đã xác định trong chương trình của mình. Hoặc bạn mô tả chúng (dưới dạng chữ) hoặc chúng là kết quả của việc áp dụng hàm cho một số giá trị khác. Bạn có thể đặt tên cho chúng (do đó xác định hằng số) để đảm bảo cùng một giá trị được sử dụng ở những nơi khác nhau trong chương trình. Lưu ý rằng ứng dụng hàm không nên được coi là một tính toán mà là kết quả của ứng dụng cho các đối số đã cho. Viết 5+2
hoặc viết 7
số tiền như nhau. Mà phù hợp với đoạn trước.
Không có biến số bắt buộc. Không có nhiệm vụ là có thể. Bạn chỉ có thể liên kết tên với các giá trị (để tạo các hằng số), không giống như các ngôn ngữ bắt buộc nơi bạn có thể liên kết tên với các biến có thể gán.
Cho dù điều đó có một chi phí phức tạp là hoàn toàn không rõ ràng. Đối với một điều, bạn tham khảo các mối quan tâm phức tạp bắt buộc. Nó không được định nghĩa như vậy đối với lập trình chức năng, trừ khi bạn chọn đọc chương trình chức năng của mình như một chương trình bắt buộc, đây không phải là mục đích của người thiết kế. Thật vậy, quan điểm chức năng nhằm mục đích cho phép bạn không lo lắng về các vấn đề như vậy và tập trung vào những gì đang được tính toán. Nó là một chút giống như đặc điểm kỹ thuật so với thực hiện.
Trình biên dịch phải quan tâm đến việc thực hiện và đủ thông minh để thích nghi tốt nhất những gì sẽ được thực hiện với phần cứng sẽ làm việc đó, bất kể đó là gì.
Tôi không nói rằng các lập trình viên không bao giờ lo lắng về điều đó. Tôi cũng không nói rằng các ngôn ngữ lập trình và công nghệ trình biên dịch đã trưởng thành như chúng ta mong muốn.
Trả lời câu hỏi
Bạn không sửa đổi giá trị hiện tại (khái niệm người ngoài hành tinh), nhưng tính toán các giá trị mới khác nhau ở nơi mong muốn, có thể bằng cách có thêm một yếu tố, đó là danh sách.
Chương trình có thể nhận dữ liệu mới. Toàn bộ vấn đề là cách bạn thể hiện điều đó bằng ngôn ngữ. Ví dụ, bạn có thể xem xét rằng chương trình hoạt động với một giá trị cụ thể, có thể không bị ràng buộc về kích thước, được gọi là luồng đầu vào. Đó là một giá trị được cho là ngồi ở đó (cho dù nó đã được biết đầy đủ hay chưa không phải là vấn đề của bạn). Sau đó, bạn có một hàm trả về một cặp bao gồm phần tử đầu tiên của luồng và phần còn lại của luồng.
Bạn có thể sử dụng điều đó để xây dựng mạng lưới các thành phần giao tiếp theo cách hoàn toàn áp dụng (coroutines)
Học máy chỉ là một vấn đề khác khi bạn phải tích lũy dữ liệu và sửa đổi giá trị. Trong lập trình chức năng, bạn không làm điều đó: bạn chỉ cần tính toán các giá trị mới khác nhau phù hợp theo dữ liệu đào tạo. Máy kết quả sẽ làm việc như là tốt. Điều bạn lo lắng là thời gian tính toán và hiệu quả không gian. Nhưng, một lần nữa, đó là một vấn đề khác, lý tưởng đó nên được xử lý bởi trình biên dịch.
Chú thích cuối
Rõ ràng, từ các ý kiến hoặc các câu trả lời khác, các ngôn ngữ lập trình chức năng thực tế không hoàn toàn là chức năng. Đó là một phản ánh về thực tế rằng công nghệ của chúng tôi vẫn sẽ được cải thiện, đặc biệt là khi biên dịch có liên quan.
Có thể viết theo phong cách ứng dụng thuần túy? Câu trả lời đã được biết đến trong khoảng 40 năm và đó là "có". Mục đích của ngữ nghĩa học biểu thị khi nó xuất hiện vào những năm 1970 là chính xác để dịch các ngôn ngữ (biên dịch) thành phong cách chức năng thuần túy, được coi là hiểu rõ hơn về mặt toán học và do đó được coi là một nguồn tài chính tốt hơn để xác định ngữ nghĩa của các chương trình.
Khía cạnh thú vị của nó là cấu trúc lập trình bắt buộc, bao gồm các biến, có thể được dịch thành một kiểu chức năng bằng cách giới thiệu các miền giá trị thích hợp, chẳng hạn như một kho lưu trữ dữ liệu. Và mặc dù phong cách chức năng, nó vẫn tương tự đáng ngạc nhiên với mã của trình biên dịch thực tế được viết theo phong cách bắt buộc.