Tôi đã đọc các bài viết Wikipedia cho cả lập trình thủ tục và lập trình chức năng , nhưng tôi vẫn hơi bối rối. Ai đó có thể đun sôi nó xuống cốt lõi?
Tôi đã đọc các bài viết Wikipedia cho cả lập trình thủ tục và lập trình chức năng , nhưng tôi vẫn hơi bối rối. Ai đó có thể đun sôi nó xuống cốt lõi?
Câu trả lời:
Một ngôn ngữ chức năng (lý tưởng) cho phép bạn viết một hàm toán học, tức là một hàm có n đối số và trả về một giá trị. Nếu chương trình được thực thi, chức năng này được đánh giá logic khi cần thiết. 1
Một ngôn ngữ thủ tục, mặt khác, thực hiện một loạt các bước tuần tự . (Có một cách để chuyển đổi logic tuần tự thành logic chức năng được gọi là kiểu chuyển tiếp .)
Kết quả là, một chương trình chức năng thuần túy luôn mang lại cùng một giá trị cho đầu vào và thứ tự đánh giá không được xác định rõ; điều đó có nghĩa là các giá trị không chắc chắn như đầu vào của người dùng hoặc giá trị ngẫu nhiên khó có thể mô hình hóa bằng các ngôn ngữ chức năng thuần túy.
1 Như mọi thứ khác trong câu trả lời này, đó là một khái quát. Thuộc tính này, đánh giá một tính toán khi cần kết quả của nó chứ không phải theo tuần tự nơi nó được gọi, được gọi là sự lười biếng. Không phải tất cả các ngôn ngữ chức năng thực sự là lười biếng, cũng như sự lười biếng bị hạn chế trong lập trình chức năng. Thay vào đó, phần mô tả được đưa ra ở đây cung cấp một khung tinh thần của người Hồi giáo để suy nghĩ về các phong cách lập trình khác nhau không phải là các thể loại riêng biệt và đối lập mà là những ý tưởng trôi chảy.
Về cơ bản hai phong cách, giống như Âm và Dương. Một cái được tổ chức, trong khi cái kia hỗn loạn. Có những tình huống khi lập trình hàm là sự lựa chọn rõ ràng và các tình huống khác là lập trình theo thủ tục là lựa chọn tốt hơn. Đây là lý do tại sao có ít nhất hai ngôn ngữ gần đây đã xuất hiện một phiên bản mới, bao gồm cả hai phong cách lập trình. ( Perl 6 và D 2 )
sub factorial ( UInt:D $n is copy ) returns UInt {
# modify "outside" state
state $call-count++;
# in this case it is rather pointless as
# it can't even be accessed from outside
my $result = 1;
loop ( ; $n > 0 ; $n-- ){
$result *= $n;
}
return $result;
}
int factorial( int n ){
int result = 1;
for( ; n > 0 ; n-- ){
result *= n;
}
return result;
}
(sao chép từ Wikipedia );
fac :: Integer -> Integer
fac 0 = 1
fac n | n > 0 = n * fac (n-1)
hoặc trong một dòng:
fac n = if n > 0 then n * fac (n-1) else 1
proto sub factorial ( UInt:D $n ) returns UInt {*}
multi sub factorial ( 0 ) { 1 }
multi sub factorial ( $n ) { $n * samewith $n-1 } # { $n * factorial $n-1 }
pure int factorial( invariant int n ){
if( n <= 1 ){
return 1;
}else{
return n * factorial( n-1 );
}
}
Factorial thực sự là một ví dụ phổ biến để cho thấy việc tạo các toán tử mới trong Perl 6 dễ dàng như thế nào giống như cách bạn tạo một chương trình con. Tính năng này đã ăn sâu vào Perl 6 đến nỗi hầu hết các nhà khai thác trong triển khai Rakudo đều được định nghĩa theo cách này. Nó cũng cho phép bạn thêm nhiều ứng cử viên của riêng bạn vào các nhà khai thác hiện có.
sub postfix:< ! > ( UInt:D $n --> UInt )
is tighter(&infix:<*>)
{ [*] 2 .. $n }
say 5!; # 120
Ví dụ này cũng cho thấy việc tạo phạm vi ( 2..$n
) và toán tử meta giảm danh sách ( [ OPERATOR ] LIST
) kết hợp với toán tử nhân infix số. ( *
)
Nó cũng cho thấy rằng bạn có thể đặt --> UInt
chữ ký thay vì returns UInt
sau nó.
(Bạn có thể thoát khỏi việc bắt đầu phạm vi với 2
"toán tử" nhân sẽ trở lại 1
khi được gọi mà không có bất kỳ đối số nào)
sub postfix:<!> ($n) { [*] 1..$n }
No operation can have side effects
-Có thể nói rõ hơn không?
sub foo( $a, $b ){ ($a,$b).pick }
← không phải lúc nào cũng trả lại cùng một đầu ra cho cùng một đầu vào, trong khi sau đâysub foo( $a, $b ){ $a + $b }
Tôi chưa bao giờ thấy định nghĩa này được đưa ra ở nơi khác, nhưng tôi nghĩ rằng điều này tổng hợp những khác biệt được đưa ra ở đây khá tốt:
Lập trình hàm tập trung vào biểu thức
Lập trình thủ tục tập trung vào các báo cáo
Biểu thức có giá trị. Một chương trình chức năng là một biểu thức mà giá trị của nó là một chuỗi các hướng dẫn để máy tính thực hiện.
Báo cáo không có giá trị và thay vào đó sửa đổi trạng thái của một số máy khái niệm.
Trong một ngôn ngữ chức năng thuần túy sẽ không có câu lệnh nào, theo nghĩa là không có cách nào để thao túng trạng thái (họ vẫn có thể có một cấu trúc cú pháp có tên là "tuyên bố", nhưng trừ khi nó thao túng trạng thái thì tôi sẽ không gọi đó là câu lệnh theo nghĩa này ). Trong một ngôn ngữ thủ tục thuần túy sẽ không có biểu thức, mọi thứ sẽ là một hướng dẫn thao túng trạng thái của máy.
Haskell sẽ là một ví dụ về ngôn ngữ chức năng thuần túy bởi vì không có cách nào để thao túng trạng thái. Mã máy sẽ là một ví dụ về ngôn ngữ thủ tục đơn thuần vì mọi thứ trong chương trình là một câu lệnh thao túng trạng thái của các thanh ghi và bộ nhớ của máy.
Phần khó hiểu là phần lớn các ngôn ngữ lập trình chứa cả biểu thức và câu lệnh, cho phép bạn trộn lẫn các mô hình. Các ngôn ngữ có thể được phân loại là nhiều chức năng hơn hoặc mang tính thủ tục hơn dựa trên mức độ chúng khuyến khích việc sử dụng câu lệnh so với biểu thức.
Ví dụ, C sẽ có nhiều chức năng hơn so với COBOL vì một lệnh gọi hàm là một biểu thức, trong khi gọi một chương trình con trong COBOL là một câu lệnh (thao túng trạng thái của các biến được chia sẻ và không trả về giá trị). Python sẽ có nhiều chức năng hơn C vì nó cho phép bạn diễn đạt logic có điều kiện dưới dạng một biểu thức bằng cách sử dụng đánh giá ngắn mạch (test && path1 | | path2 trái ngược với câu lệnh if). Lược đồ sẽ có nhiều chức năng hơn Python vì mọi thứ trong lược đồ là một biểu thức.
Bạn vẫn có thể viết theo phong cách chức năng bằng ngôn ngữ khuyến khích mô hình thủ tục và ngược lại. Thật khó khăn và / hoặc khó xử hơn khi viết theo một mô hình không được khuyến khích bởi ngôn ngữ này.
Trong khoa học máy tính, lập trình chức năng là một mô hình lập trình coi việc tính toán là việc đánh giá các hàm toán học và tránh trạng thái và dữ liệu đột biến. Nó nhấn mạnh việc áp dụng các chức năng, trái ngược với phong cách lập trình thủ tục nhấn mạnh những thay đổi về trạng thái.
GetUserContext()
vào hàm, bối cảnh người dùng sẽ được truyền vào. Đây có phải là lập trình chức năng không? Cảm ơn trước.
Tôi tin rằng lập trình thủ tục / chức năng / mục tiêu là về cách tiếp cận một vấn đề.
Kiểu đầu tiên sẽ lên kế hoạch cho mọi thứ theo từng bước và giải quyết vấn đề bằng cách thực hiện từng bước (một thủ tục) tại một thời điểm. Mặt khác, lập trình chức năng sẽ nhấn mạnh cách tiếp cận phân chia và chinh phục, trong đó vấn đề được chia thành vấn đề phụ, sau đó từng vấn đề phụ được giải quyết (tạo ra một chức năng để giải quyết vấn đề phụ đó) và kết quả được kết hợp để tạo câu trả lời cho toàn bộ vấn đề Cuối cùng, lập trình Mục tiêu sẽ mô phỏng thế giới thực bằng cách tạo ra một thế giới nhỏ bên trong máy tính với nhiều đối tượng, mỗi đối tượng có một đặc điểm (phần nào) duy nhất và tương tác với các thế giới khác. Từ những tương tác đó, kết quả sẽ xuất hiện.
Mỗi phong cách lập trình đều có những ưu điểm và nhược điểm riêng. Do đó, làm một cái gì đó như "lập trình thuần túy" (nghĩa là hoàn toàn theo thủ tục - không ai làm điều này, điều này thật kỳ lạ - hoặc hoàn toàn là chức năng hoặc hoàn toàn khách quan), rất khó, trừ một số vấn đề cơ bản đặc biệt được thiết kế để thể hiện lợi thế của phong cách lập trình (do đó, chúng tôi gọi những người thích sự thuần khiết là "weenie": D).
Sau đó, từ những kiểu đó, chúng tôi có các ngôn ngữ lập trình được thiết kế để tối ưu hóa cho một số kiểu. Ví dụ, hội là tất cả về thủ tục. Được rồi, hầu hết các ngôn ngữ đầu tiên là thủ tục, không chỉ Asm, như C, Pascal, (và Fortran, tôi đã nghe). Sau đó, chúng ta có tất cả Java nổi tiếng trong trường mục tiêu (Trên thực tế, Java và C # cũng nằm trong một lớp gọi là "định hướng tiền", nhưng đó là chủ đề cho một cuộc thảo luận khác). Ngoài ra mục tiêu là Smalltalk. Trong trường chức năng, chúng ta sẽ có "gần như chức năng" (một số người coi chúng là không trong sạch) Gia đình Lisp và gia đình ML và nhiều "Haskell, Erlang" hoàn toàn chức năng, v.v. Nhân tiện, có nhiều ngôn ngữ chung như Perl, Python , Ruby.
num = 1
def function_to_add_one(num):
num += 1
return num
function_to_add_one(num)
function_to_add_one(num)
function_to_add_one(num)
function_to_add_one(num)
function_to_add_one(num)
#Final Output: 2
num = 1
def procedure_to_add_one():
global num
num += 1
return num
procedure_to_add_one()
procedure_to_add_one()
procedure_to_add_one()
procedure_to_add_one()
procedure_to_add_one()
#Final Output: 6
function_to_add_one
là một chức năng
procedure_to_add_one
là một thủ tục
Ngay cả khi bạn chạy chức năng năm lần, mỗi lần nó sẽ trả về 2
Nếu bạn chạy thủ tục năm lần, vào cuối lần chạy thứ năm, nó sẽ cung cấp cho bạn 6 lần .
Để mở rộng nhận xét của Konrad:
Kết quả là, một chương trình chức năng thuần túy luôn mang lại cùng một giá trị cho đầu vào và thứ tự đánh giá không được xác định rõ;
Bởi vì điều này, mã chức năng thường dễ song song hơn. Vì không có (nói chung) không có tác dụng phụ của các chức năng và chúng (nói chung) chỉ hành động theo lập luận của chúng, rất nhiều vấn đề đồng thời biến mất.
Lập trình hàm cũng được sử dụng khi bạn cần có khả năng chứng minh mã của mình là chính xác. Điều này khó hơn nhiều để làm với lập trình thủ tục (không dễ với chức năng, nhưng vẫn dễ hơn).
Tuyên bố miễn trừ trách nhiệm: Tôi đã không sử dụng lập trình chức năng trong nhiều năm và chỉ gần đây mới bắt đầu xem xét lại, vì vậy tôi có thể không hoàn toàn chính xác ở đây. :)
Một điều tôi chưa từng thấy thực sự nhấn mạnh ở đây là các ngôn ngữ chức năng hiện đại như Haskell thực sự nhiều hơn về các chức năng hạng nhất để kiểm soát luồng hơn là đệ quy rõ ràng. Bạn không cần xác định giai đoạn đệ quy trong Haskell, như đã làm ở trên. Tôi nghĩ một cái gì đó như
fac n = foldr (*) 1 [1..n]
là một cấu trúc hoàn toàn thành ngữ và gần gũi hơn về mặt tinh thần khi sử dụng một vòng lặp hơn là sử dụng đệ quy rõ ràng.
Các ngôn ngữ thủ tục có xu hướng theo dõi trạng thái (sử dụng các biến) và có xu hướng thực thi như một chuỗi các bước. Các ngôn ngữ chức năng thuần túy không theo dõi trạng thái, sử dụng các giá trị bất biến và có xu hướng thực thi như một loạt các phụ thuộc. Trong nhiều trường hợp, trạng thái của ngăn xếp cuộc gọi sẽ chứa thông tin tương đương với thông tin sẽ được lưu trữ trong các biến trạng thái trong mã thủ tục.
Đệ quy là một ví dụ cổ điển của lập trình kiểu chức năng.
Konrad nói:
Kết quả là, một chương trình chức năng thuần túy luôn mang lại cùng một giá trị cho đầu vào và thứ tự đánh giá không được xác định rõ; điều đó có nghĩa là các giá trị không chắc chắn như đầu vào của người dùng hoặc giá trị ngẫu nhiên khó có thể mô hình hóa bằng các ngôn ngữ chức năng thuần túy.
Thứ tự đánh giá trong một chương trình chức năng thuần túy có thể khó (lý do) về lý do (đặc biệt là với sự lười biếng) hoặc thậm chí không quan trọng nhưng tôi nghĩ rằng việc nói nó không được xác định rõ ràng có vẻ như bạn không thể biết nếu chương trình của bạn đang diễn ra để làm việc
Có lẽ một lời giải thích tốt hơn là dòng điều khiển trong các chương trình chức năng được dựa trên khi giá trị của các đối số của hàm là cần thiết. Điều tốt về điều này trong các chương trình được viết tốt, trạng thái trở nên rõ ràng: mỗi hàm liệt kê các đầu vào của nó dưới dạng tham số thay vì tự ý trộn trạng thái toàn cầu. Vì vậy, ở một mức độ nào đó , sẽ dễ dàng hơn để suy luận về thứ tự đánh giá đối với một chức năng tại một thời điểm . Mỗi chức năng có thể bỏ qua phần còn lại của vũ trụ và tập trung vào những gì nó cần làm. Khi được kết hợp, các chức năng được đảm bảo hoạt động giống như [1] giống như khi hoạt động.
... Các giá trị không chắc chắn như đầu vào của người dùng hoặc giá trị ngẫu nhiên khó có thể mô hình hóa bằng các ngôn ngữ chức năng thuần túy.
Giải pháp cho vấn đề đầu vào trong các chương trình chức năng thuần túy là nhúng ngôn ngữ bắt buộc dưới dạng DSL bằng cách sử dụng một sự trừu tượng hóa đủ mạnh . Trong các ngôn ngữ bắt buộc (hoặc không thuần túy), điều này là không cần thiết bởi vì bạn có thể "gian lận" và vượt qua trạng thái ngầm và thứ tự đánh giá là rõ ràng (dù bạn có thích hay không). Do "gian lận" và đánh giá bắt buộc tất cả các tham số cho mọi chức năng, trong các ngôn ngữ bắt buộc 1) bạn mất khả năng tạo cơ chế luồng điều khiển của riêng mình (không có macro), mã 2) vốn không phải là luồng an toàn và / hoặc song song theo mặc định, 3) và thực hiện một cái gì đó như hoàn tác (du hành thời gian) cần có công việc cẩn thận (lập trình viên bắt buộc phải lưu trữ một công thức để lấy lại (các) giá trị cũ!), Trong khi lập trình chức năng thuần túy mua cho bạn tất cả những thứ này và một vài điều nữa tôi có thể đã quên mất "miễn phí".
Tôi hy vọng điều này không giống như nhiệt tâm, tôi chỉ muốn thêm một số quan điểm. Lập trình bắt buộc và đặc biệt là lập trình mô hình hỗn hợp trong các ngôn ngữ mạnh mẽ như C # 3.0 vẫn là những cách hoàn toàn hiệu quả để hoàn thành công việc và không có viên đạn bạc .
[1] ... ngoại trừ có thể với việc sử dụng bộ nhớ tôn trọng (xem Foldl và Foldl 'trong Haskell).
Để mở rộng nhận xét của Konrad:
và thứ tự đánh giá không được xác định rõ
Một số ngôn ngữ chức năng có những gì được gọi là Đánh giá lười biếng. Điều đó có nghĩa là một hàm không được thực thi cho đến khi giá trị là cần thiết. Cho đến thời điểm đó, chức năng chính là những gì được thông qua xung quanh.
Ngôn ngữ thủ tục là bước 1 bước 2 bước 3 ... nếu ở bước 2 bạn nói thêm 2 + 2, thì nó sẽ thực hiện ngay sau đó. Trong đánh giá lười biếng, bạn sẽ nói thêm 2 + 2, nhưng nếu kết quả không bao giờ được sử dụng, nó không bao giờ thực hiện phép cộng.
Nếu bạn có cơ hội, tôi sẽ giới thiệu và nhận một bản sao của Lisp / Scheme và thực hiện một số dự án trong đó. Hầu hết các ý tưởng gần đây đã trở thành bandwagons đã được thể hiện trong Lisp nhiều thập kỷ trước: lập trình chức năng, tiếp tục (như đóng cửa), thu gom rác, thậm chí là XML.
Vì vậy, đó sẽ là một cách tốt để bắt đầu với tất cả những ý tưởng hiện tại này, và một vài điều nữa bên cạnh, như tính toán tượng trưng.
Bạn nên biết lập trình chức năng nào tốt và cái gì không tốt. Nó không tốt cho mọi thứ. Một số vấn đề được thể hiện tốt nhất về mặt tác dụng phụ, trong đó cùng một câu hỏi đưa ra câu trả lời khác nhau tùy thuộc vào thời điểm nó được hỏi.
@Creighton:
Trong Haskell có một chức năng thư viện gọi là sản phẩm :
prouduct list = foldr 1 (*) list
hoặc đơn giản:
product = foldr 1 (*)
vì vậy giai thừa "thành ngữ"
fac n = foldr 1 (*) [1..n]
đơn giản là
fac n = product [1..n]
Lập trình thủ tục chia các chuỗi các câu lệnh và các cấu trúc có điều kiện thành các khối riêng biệt được gọi là các thủ tục được tham số hóa qua các đối số là các giá trị (không có chức năng).
Lập trình hàm là như nhau ngoại trừ các hàm là các giá trị hạng nhất, vì vậy chúng có thể được truyền dưới dạng đối số cho các hàm khác và được trả về dưới dạng kết quả từ các lệnh gọi hàm.
Lưu ý rằng lập trình chức năng là một khái quát của lập trình thủ tục trong giải thích này. Tuy nhiên, một thiểu số giải thích "lập trình chức năng" có nghĩa là không có tác dụng phụ, khá khác biệt nhưng không liên quan đến tất cả các ngôn ngữ chức năng chính trừ Haskell.
Để hiểu được sự khác biệt, người ta cần hiểu rằng mô hình "cha đỡ đầu" của cả lập trình thủ tục và chức năng là lập trình bắt buộc .
Về cơ bản lập trình thủ tục chỉ là một cách cấu trúc các chương trình bắt buộc trong đó phương pháp trừu tượng chính là "thủ tục". (hoặc "hàm" trong một số ngôn ngữ lập trình). Ngay cả Lập trình hướng đối tượng cũng chỉ là một cách khác để cấu trúc một chương trình bắt buộc, trong đó trạng thái được gói gọn trong các đối tượng, trở thành một đối tượng với "trạng thái hiện tại", cộng với đối tượng này có một tập hợp các hàm, phương thức và các công cụ khác cho phép bạn lập trình viên thao tác hoặc cập nhật trạng thái.
Bây giờ, liên quan đến lập trình chức năng, ý chính trong cách tiếp cận của nó là nó xác định những giá trị nào cần thực hiện và cách chuyển các giá trị này. (vì vậy không có trạng thái và không có dữ liệu có thể thay đổi vì nó lấy các hàm làm giá trị lớp đầu tiên và chuyển chúng làm tham số cho các hàm khác).
PS: hiểu mọi mô hình lập trình được sử dụng để làm rõ sự khác biệt giữa tất cả chúng.
PSS: Vào cuối ngày, các mô hình lập trình chỉ là những cách tiếp cận khác nhau để giải quyết vấn đề.
PSS: này câu trả lời Quora có một lời giải thích tuyệt vời.
Không có câu trả lời nào ở đây cho thấy lập trình chức năng thành ngữ. Câu trả lời nhân tử đệ quy là tuyệt vời để biểu diễn đệ quy trong FP, nhưng phần lớn mã không được đệ quy nên tôi không nghĩ rằng câu trả lời đó hoàn toàn đại diện.
Giả sử bạn có một mảng các chuỗi và mỗi chuỗi đại diện cho một số nguyên như "5" hoặc "-200". Bạn muốn kiểm tra mảng đầu vào của chuỗi này so với trường hợp kiểm tra nội bộ của bạn (Sử dụng so sánh số nguyên). Cả hai giải pháp được hiển thị dưới đây
arr_equal(a : [Int], b : [Str]) -> Bool {
if(a.len != b.len) {
return false;
}
bool ret = true;
for( int i = 0; i < a.len /* Optimized with && ret*/; i++ ) {
int a_int = a[i];
int b_int = parseInt(b[i]);
ret &= a_int == b_int;
}
return ret;
}
eq = i, j => i == j # This is usually a built-in
toInt = i => parseInt(i) # Of course, parseInt === toInt here, but this is for visualization
arr_equal(a : [Int], b : [Str]) -> Bool =
zip(a, b.map(toInt)) # Combines into [Int, Int]
.map(eq)
.reduce(true, (i, j) => i && j) # Start with true, and continuously && it with each value
Mặc dù các ngôn ngữ chức năng thuần túy nói chung là ngôn ngữ nghiên cứu (Vì thế giới thực thích tác dụng phụ miễn phí), ngôn ngữ thủ tục trong thế giới thực sẽ sử dụng cú pháp chức năng đơn giản hơn nhiều khi thích hợp.
Điều này thường được thực hiện với một thư viện bên ngoài như Lodash hoặc có sẵn với các ngôn ngữ mới hơn như Rust . Đòn bẩy trong lập trình chức năng được thực hiện với các chức năng / khái niệm như map
, filter
, reduce
, currying
, partial
, cuối cùng ba trong số đó bạn có thể tra cứu để hiểu biết thêm.
Để được sử dụng trong tự nhiên, trình biên dịch thường sẽ phải tìm cách chuyển đổi phiên bản chức năng thành phiên bản thủ tục trong nội bộ, vì chi phí gọi hàm quá cao. Các trường hợp đệ quy như giai thừa được hiển thị sẽ sử dụng các thủ thuật như gọi đuôi để loại bỏ việc sử dụng bộ nhớ O (n). Thực tế là không có tác dụng phụ cho phép các trình biên dịch chức năng thực hiện && ret
tối ưu hóa ngay cả khi việc .reduce
này được thực hiện lần cuối. Sử dụng Lodash trong JS, rõ ràng không cho phép bất kỳ tối ưu hóa nào, do đó, nó ảnh hưởng đến hiệu suất (Điều này thường không phải là mối quan tâm với phát triển web). Các ngôn ngữ như Rust sẽ tối ưu hóa nội bộ (Và có các chức năng như try_fold
hỗ trợ && ret
tối ưu hóa).