Tại sao các chương trình chức năng có mối tương quan giữa thành công biên dịch và tính đúng đắn?


12

Tôi đã có xu hướng lập trình chức năng được 4 năm rồi, kể từ khi tôi bắt đầu làm việc với LINQ. Gần đây, tôi đã viết một số mã C # chức năng thuần túy và tôi nhận thấy, trước tiên, những gì tôi đã đọc về các chương trình chức năng - rằng một khi chúng biên dịch chúng có xu hướng chính xác.

Tôi đã cố gắng đặt một ngón tay vào lý do tại sao đây là trường hợp nhưng tôi đã không thành công.

Một dự đoán là khi áp dụng các nguyên tắc OO, bạn có một "lớp trừu tượng" không có trong các chương trình chức năng và lớp trừu tượng này làm cho các hợp đồng giữa các đối tượng có thể chính xác trong khi thực hiện sai.

Có ai nghĩ về điều này và đưa ra lý do trừu tượng cơ bản cho mối tương quan giữa thành công biên dịch và tính chính xác của chương trình trong lập trình chức năng không?


9
Lisp là một ngôn ngữ chức năng, nhưng nó không có kiểm tra thời gian biên dịch để nói. Tương tự cho một vài ngôn ngữ chức năng khác. Một đặc điểm chính xác hơn của các ngôn ngữ mà bạn nói đến sẽ là: Các ngôn ngữ có hệ thống loại chính thức mạnh mẽ (ít nhất là Hindley-Milner).

1
@delnan Tôi không nói rằng Lisp là ngôn ngữ lập trình chức năng, mặc dù nó có thể được sử dụng để viết mã lập trình chức năng. Clojure là một phương ngữ Lisp là ngôn ngữ lập trình chức năng
sakisk

2
Tôi đồng ý với @delnan. Tuyên bố này liên quan nhiều hơn với các ngôn ngữ lập trình chức năng được gõ tĩnh, đặc biệt là Haskell sử dụng hệ thống Hindley-Milner. Tôi nghĩ rằng ý tưởng chính là nếu bạn chọn đúng loại, sự tự tin rằng chương trình của bạn là chính xác được tăng lên.
sakisk

1
Mã chức năng có thể có nhiều trừu tượng và các chỉ định như mã OOP chính thống, điển hình của bạn (nếu không nhiều hơn). Ma quỷ nằm trong các chi tiết - ít tác dụng phụ hơn và không có nghĩa là không có trạng thái vô hình để theo dõi và ít cơ hội để bắt vít hơn. Lưu ý rằng bạn có thể áp dụng các nguyên tắc tương tự trong các ngôn ngữ bắt buộc chính thống, đó chỉ là công việc nhiều hơn và thường dài dòng hơn (ví dụ: phải tát finalvào mọi thứ).
Doval

1
Không phải là một câu trả lời đầy đủ nhưng gõ chương trình hình thức xác minh chính thức của chương trình. Nói chung, các chương trình hướng đối tượng có các hệ thống loại phức tạp hoặc rất đơn giản vì sự thay thế cần phải được tính đến - trong hầu hết các trường hợp chúng không được thực hiện vì sự thuận tiện. OTOH các hệ thống giống ML có thể sử dụng CH đến mức tối đa để bạn có thể mã hóa một bằng chứng chỉ trong các loại và sử dụng trình biên dịch làm trình kiểm tra bằng chứng.
Maciej Piechotka

Câu trả lời:


12

Tôi có thể viết câu trả lời này như một người chứng minh mọi thứ rất nhiều, vì vậy với tôi tính đúng đắn không chỉ là những gì hoạt động, mà là những gì hoạt động và dễ chứng minh.

Trong rất nhiều giác quan, lập trình chức năng là hạn chế hơn sau đó lập trình bắt buộc. Rốt cuộc, không có gì ngăn bạn không bao giờ biến đổi một biến trong C! Thật vậy, hầu hết các tính năng trong các ngôn ngữ FP đều được nói thẳng về mặt chỉ một vài tính năng cốt lõi. Tất cả khá sôi sục với lambdas, ứng dụng chức năng và khớp mẫu!

Tuy nhiên, vì chúng tôi đã trả tiền trước cho piper, chúng tôi có rất ít để giải quyết và chúng tôi có ít lựa chọn hơn cho việc mọi thứ có thể đi sai. Nếu bạn là một người hâm mộ năm 1984, tự do thực sự là nô lệ! Bằng cách sử dụng 101 thủ thuật gọn gàng khác nhau cho một chương trình, chúng tôi phải suy luận về mọi thứ như thể bất kỳ điều nào trong số 101 điều này có thể xảy ra! Điều đó thực sự khó thực hiện khi nó bật ra :)

Nếu bạn bắt đầu với kéo an toàn thay vì kiếm, chạy sẽ ít nguy hiểm hơn.

Bây giờ chúng tôi xem xét câu hỏi của bạn: làm thế nào để tất cả những điều này phù hợp với "nó biên dịch và hoạt động!" hiện tượng. Tôi nghĩ rằng một phần lớn của điều này là cùng một lý do tại sao nó dễ dàng chứng minh mã! Rốt cuộc, khi bạn viết phần mềm, bạn đang xây dựng một số bằng chứng không chính thức rằng nó đúng. Bởi vì điều này được bao phủ bởi bằng chứng bằng tay tự nhiên của bạn và trình biên dịch sở hữu khái niệm về tính chính xác (đánh máy) là khá nhiều.

Khi bạn thêm các tính năng và các tương tác phức tạp giữa chúng, những gì không được kiểm tra bởi hệ thống loại sẽ tăng lên. Tuy nhiên, khả năng của bạn để xây dựng bằng chứng không chính thức dường như không được cải thiện! Điều này có nghĩa là có nhiều thứ có thể lọt qua kiểm tra ban đầu của bạn và phải bị bắt sau đó.


1
Tôi thích câu trả lời của bạn nhưng tôi không thấy cách nó trả lời câu hỏi của OP
sakisk

1
@faif Mở rộng câu trả lời của tôi. TLDR: tất cả mọi người là một nhà toán học.
Daniel Gratzer

"Bằng cách sử dụng 101 thủ thuật gọn gàng khác nhau cho một chương trình, chúng tôi phải suy luận về những điều như thể bất kỳ điều nào trong số 101 điều này có thể xảy ra!": Tôi đọc được ở đâu đó rằng bạn cần phải là một thiên tài để lập trình với sự đột biến, bởi vì bạn phải giữ như vậy nhiều thông tin trong đầu của bạn.
Giorgio

12

lý do trừu tượng cơ bản cho mối tương quan giữa thành công biên dịch và tính chính xác của chương trình trong lập trình chức năng?

Nhà nước đột biến.

Trình biên dịch kiểm tra mọi thứ tĩnh. Họ đảm bảo rằng chương trình của bạn được hình thành tốt và hệ thống loại cung cấp một cơ chế để cố gắng đảm bảo rằng loại giá trị phù hợp được cho phép ở đúng nơi. Hệ thống loại cũng cố gắng đảm bảo rằng loại ngữ nghĩa phù hợp được cho phép ở đúng nơi.

Ngay khi chương trình của bạn giới thiệu trạng thái, ràng buộc sau đó trở nên ít hữu ích hơn. Bạn không chỉ cần lo lắng về các giá trị phù hợp ở đúng điểm, mà bạn còn cần tính đến sự thay đổi giá trị đó tại các điểm tùy ý trong chương trình của bạn. Bạn cần tính đến ngữ nghĩa của mã của bạn thay đổi cùng với trạng thái đó.

Nếu bạn đang lập trình chức năng tốt, sẽ không có (hoặc rất ít) trạng thái có thể thay đổi.

Có một số tranh luận mặc dù về nguyên nhân ở đây - nếu các chương trình không có trạng thái hoạt động sau khi biên dịch thường xuyên hơn vì trình biên dịch có thể bắt được nhiều lỗi hơn hoặc nếu các chương trình không có trạng thái hoạt động sau khi biên dịch thường xuyên hơn vì kiểu lập trình đó tạo ra ít lỗi hơn.

Đó có thể là sự pha trộn của cả hai trong kinh nghiệm của tôi.


2
"Đó có thể là sự pha trộn của cả hai trong trải nghiệm của tôi.": Tôi có cùng trải nghiệm. Gõ tĩnh bắt lỗi tại thời gian biên dịch khi sử dụng ngôn ngữ bắt buộc (ví dụ Pascal). Trong FP, việc tránh tính đột biến và, tôi sẽ nói thêm, việc sử dụng một kiểu lập trình khai báo nhiều hơn giúp cho việc lập luận về mã dễ dàng hơn. Nếu một ngôn ngữ cung cấp cả hai, bạn có được cả hai lợi thế.
Giorgio

7

Nói một cách đơn giản, các hạn chế có nghĩa là có ít cách chính xác hơn để đặt mọi thứ lại với nhau và các hàm hạng nhất giúp dễ dàng xác định những thứ như cấu trúc vòng lặp. Lấy vòng lặp từ câu trả lời này , ví dụ:

for (Iterator<String> iterator = list.iterator(); iterator.hasNext();) {
    String string = iterator.next();
    if (string.isEmpty()) {
        iterator.remove();
    }
}

Đây có thể là một cách bắt buộc an toàn trong Java để loại bỏ một phần tử khỏi bộ sưu tập trong khi bạn đang lặp qua nó. Có rất nhiều cách nhìn rất gần, nhưng đều sai. Mọi người không biết về phương pháp này đôi khi trải qua các cách phức tạp để tránh vấn đề, như lặp đi lặp lại qua một bản sao.

Việc tạo ra cái chung này không quá khó, vì vậy nó sẽ hoạt động trên nhiều bộ sưu tập Strings, nhưng không có chức năng hạng nhất, bạn không thể thay thế vị ngữ (điều kiện bên trong if), vì vậy mã này có xu hướng bị sao chép và dán và sửa đổi một chút.

Kết hợp các hàm hạng nhất cung cấp cho bạn khả năng truyền vị ngữ làm tham số, với việc hạn chế tính không thay đổi sẽ gây khó chịu nếu bạn không và bạn đưa ra các khối xây dựng đơn giản filternhư trong mã Scala này điều đó cũng làm điều tương tự:

list filter (!_.isEmpty)

Bây giờ hãy nghĩ về những gì hệ thống loại kiểm tra cho bạn, tại thời điểm biên dịch trong trường hợp của Scala, nhưng những kiểm tra này cũng được thực hiện bởi các hệ thống loại động trong lần đầu tiên bạn chạy nó:

  • listphải là một số loại hỗ trợ filterphương thức, cụ thể là một bộ sưu tập.
  • Các phần tử của listphải có một isEmptyphương thức trả về boolean.
  • Đầu ra sẽ là một bộ sưu tập nhỏ hơn (có khả năng) với cùng loại phần tử.

Một khi những điều đó đã được kiểm tra, những cách khác còn lại để lập trình viên có thể làm hỏng? Tôi vô tình quên mất !, điều này gây ra một trường hợp thử nghiệm cực kỳ rõ ràng. Đó là khá nhiều lỗi duy nhất có sẵn và tôi chỉ mắc lỗi vì tôi đang dịch trực tiếp từ mã đã kiểm tra điều kiện nghịch đảo.

Mô hình này được lặp đi lặp lại nhiều lần. Các chức năng hạng nhất cho phép bạn cấu trúc lại mọi thứ thành các tiện ích nhỏ có thể tái sử dụng với ngữ nghĩa chính xác, các hạn chế như tính bất biến cung cấp cho bạn động lực để thực hiện và nhập kiểm tra các tham số của các tiện ích đó để lại rất ít chỗ trống.

Tất nhiên, tất cả phụ thuộc vào lập trình viên biết rằng chức năng đơn giản hóa như filterđã tồn tại và có thể tìm thấy nó, hoặc nhận ra lợi ích của việc tự tạo một cái. Cố gắng tự thực hiện điều này ở mọi nơi chỉ bằng cách sử dụng đệ quy đuôi và bạn quay lại cùng một chiếc thuyền phức tạp như phiên bản bắt buộc, chỉ tệ hơn. Chỉ vì bạn có thể viết nó rất đơn giản, không có nghĩa là phiên bản đơn giản là hiển nhiên.


"Một khi những điều đó đã được kiểm tra, những cách khác để lập trình viên có thể làm hỏng?": Điều này bằng cách nào đó xác nhận kinh nghiệm của tôi rằng (1) kiểu gõ tĩnh + (2) kiểu cách để lại ít cách hơn để làm hỏng mọi thứ. Kết quả là tôi có xu hướng nhận được một chương trình chính xác nhanh hơn và cần phải viết các bài kiểm tra đơn vị ít hơn khi sử dụng FP.
Giorgio

2

Tôi không nghĩ rằng có một mối tương quan đáng kể giữa biên dịch lập trình chức năng và tính chính xác của thời gian chạy. Có thể có một số mối tương quan giữa biên dịch gõ tĩnh và tính chính xác của thời gian chạy, vì ít nhất bạn có thể có đúng loại, nếu bạn không truyền.

Khía cạnh ngôn ngữ lập trình bằng cách nào đó có thể tương quan với việc biên dịch thành công với độ chính xác của kiểu thời gian chạy, như bạn mô tả, là kiểu gõ tĩnh, và thậm chí sau đó, chỉ khi bạn không làm yếu trình kiểm tra kiểu với các phôi chỉ có thể được xác nhận khi chạy (trong môi trường với các giá trị hoặc vị trí được gõ mạnh, ví dụ Java hoặc .Net) hoặc hoàn toàn không (trong các môi trường mà thông tin loại bị mất hoặc với kiểu gõ yếu, ví dụ C và C ++).

Tuy nhiên, lập trình chức năng mỗi lần có thể giúp theo những cách khác, chẳng hạn như tránh dữ liệu được chia sẻ và trạng thái có thể thay đổi.

Cả hai khía cạnh với nhau có thể có một mối tương quan đáng kể về tính chính xác, nhưng bạn phải lưu ý rằng việc không có lỗi biên dịch và thời gian chạy không nói lên điều gì, nói đúng ra, về tính chính xác theo nghĩa rộng hơn, như trong chương trình thực hiện những gì nó phải làm và thất bại nhanh trên đầu vào không hợp lệ hoặc lỗi thời gian chạy không thể kiểm soát. Đối với điều đó, bạn cần các quy tắc kinh doanh, yêu cầu, trường hợp sử dụng, xác nhận, kiểm tra đơn vị, kiểm tra tích hợp, v.v ... Cuối cùng, ít nhất, theo tôi, chúng cung cấp sự tự tin hơn nhiều so với lập trình chức năng, gõ tĩnh hoặc cả hai.


Điều này. Tính đúng đắn của một chương trình không thể được đánh giá bằng cách biên dịch thành công. Nếu một trình biên dịch có thể hiểu các yêu cầu thường mâu thuẫn và không chính xác của mỗi người đã đóng góp vào các thông số kỹ thuật của chương trình, thì có lẽ việc biên dịch thành công có thể được coi là chính xác. Nhưng trình biên dịch huyền thoại đó sẽ không cần một lập trình viên! Mặc dù có thể có một mối tương quan tổng thể cao hơn một chút giữa biên dịch và tính chính xác cho các chương trình chức năng và mệnh lệnh, nhưng đó là một phần nhỏ trong tổng số phán đoán chính xác mà tôi nghĩ rằng về cơ bản nó không liên quan
Jordan Rieger

2

Giải thích cho các nhà quản lý:

Một chương trình chức năng giống như một cỗ máy lớn, nơi mọi thứ được kết nối, ống, dây cáp. [Xe hơi]

Một chương trình thủ tục giống như một tòa nhà với các phòng chứa một cỗ máy nhỏ, lưu trữ một phần sản phẩm trong thùng, lấy một phần sản phẩm từ nơi khác. [Nhà máy]

Vì vậy, khi máy chức năng đã khớp với nhau: nó bị ràng buộc để sản xuất một cái gì đó. Nếu một phức hợp thủ tục chạy, bạn có thể đã giám sát các hiệu ứng cụ thể, hỗn loạn được giới thiệu, không được đảm bảo chức năng. Ngay cả khi bạn có một danh sách kiểm tra tất cả mọi thứ được tích hợp chính xác, vẫn có rất nhiều trạng thái, tình huống có thể xảy ra (một phần sản phẩm nằm xung quanh, tràn xô, thiếu), điều đó rất khó để đưa ra.


Nhưng nghiêm túc, mã thủ tục không chỉ định ngữ nghĩa của kết quả mong muốn nhiều như mã chức năng. Các lập trình viên thủ tục có thể dễ dàng thoát khỏi mã và dữ liệu hoàn cảnh hơn, và giới thiệu một số cách để làm một việc (một số trong số đó không hoàn hảo). Thông thường dữ liệu không liên quan được tạo ra. Lập trình viên chức năng có thể mất nhiều thời gian hơn khi vấn đề trở nên phức tạp hơn?

Một ngôn ngữ chức năng được gõ mạnh vẫn có thể thực hiện phân tích luồng và dữ liệu tốt hơn. Với một ngôn ngữ thủ tục, mục tiêu của một chương trình thường phải được xác định bên ngoài chương trình, như là một phân tích chính xác chính thức.


1
Anology tốt hơn: Lập trình chức năng giống như một bàn trợ giúp không có khách hàng - mọi thứ đều tuyệt vời (miễn là bạn không đặt câu hỏi về mục đích hay hiệu quả).
Brendan

@Brendan xe & nhà máy không hình thành một sự tương tự xấu như vậy. Nó cố gắng giải thích tại sao các chương trình (quy mô nhỏ) trong một ngôn ngữ chức năng có nhiều khả năng hoạt động và ít bị lỗi hơn so với "nhà máy". Nhưng để giải cứu OOP nói rằng một nhà máy có thể sản xuất một số thứ và lớn hơn. So sánh của bạn là apt; tần suất người ta nghe thấy FP có thể song song và tối ưu hóa mạnh mẽ nhưng thực tế (không chơi chữ) mang lại kết quả chậm. Tôi vẫn giữ cho FP.
Eggen

Lập trình chức năng ở quy mô hoạt động khá tốt cho một en.wikipedia.org/wiki/Spherical_cow Giữ cho nó cục bộ.
Den

@Den Bản thân tôi sẽ không sợ vấn đề khả thi khi làm việc trong một dự án FP quy mô lớn. Thậm chí yêu nó. Khái quát hóa có giới hạn của nó. Nhưng như tuyên bố cuối cùng cũng là một khái quát ... (cảm ơn vì con bò hình cầu)
Joop Eggen
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.