Các thuật ngữ chức năng, khai báo và lập trình mệnh lệnh có nghĩa là gì?
Các thuật ngữ chức năng, khai báo và lập trình mệnh lệnh có nghĩa là gì?
Câu trả lời:
Tại thời điểm viết bài này, các câu trả lời được bình chọn hàng đầu trên trang này là không chính xác và sai lầm về định nghĩa khai báo so với định nghĩa bắt buộc, bao gồm cả câu trả lời trích dẫn Wikipedia. Một số câu trả lời đang giới thiệu các thuật ngữ theo những cách khác nhau.
Cũng tham khảo giải thích của tôi về lý do tại sao lập trình bảng tính là khai báo, bất kể các công thức làm biến đổi các ô.
Ngoài ra, một số câu trả lời cho rằng lập trình chức năng phải là một tập hợp con của khai báo. Về điểm đó, nó phụ thuộc vào việc chúng ta phân biệt "chức năng" với "thủ tục". Hãy xử lý mệnh lệnh so với khai báo trước.
Định nghĩa biểu thức khai báo
Các chỉ thuộc tính mà có thể có thể phân biệt một tường thuật biểu hiện từ một mệnh lệnh biểu thức là tính minh bạch tham chiếu (RT) của tiểu biểu của nó. Tất cả các thuộc tính khác được chia sẻ giữa cả hai loại biểu thức hoặc xuất phát từ RT.
Ngôn ngữ khai báo 100% (nghĩa là ngôn ngữ trong đó mọi biểu thức có thể là RT) không (trong số các yêu cầu RT khác) cho phép đột biến các giá trị được lưu trữ, ví dụ HTML và hầu hết Haskell.
Định nghĩa biểu thức RT
RT thường được gọi là "không có tác dụng phụ". Thuật ngữ hiệu ứng không có định nghĩa chính xác, vì vậy một số người không đồng ý rằng "không có tác dụng phụ" giống như RT. RT có một định nghĩa chính xác .
Do mọi biểu thức con về mặt khái niệm là một lệnh gọi hàm, RT yêu cầu việc thực hiện một hàm (tức là (các) biểu thức bên trong hàm được gọi) không thể truy cập trạng thái có thể thay đổi bên ngoài hàm (truy cập trạng thái cục bộ có thể thay đổi là được phép). Nói một cách đơn giản, hàm (thực hiện) phải thuần túy .
Định nghĩa hàm thuần
Một chức năng thuần túy thường được cho là "không có tác dụng phụ". Các hiệu ứng thuật ngữ không có định nghĩa chính xác, vì vậy một số người không đồng ý.
Hàm thuần có các thuộc tính sau.
Hãy nhớ rằng RT áp dụng cho các biểu thức (bao gồm các lệnh gọi hàm) và độ tinh khiết áp dụng cho các hàm (triển khai).
Một ví dụ tối nghĩa của các hàm không tinh khiết tạo ra các biểu thức RT là đồng thời, nhưng điều này là do độ tinh khiết bị phá vỡ ở lớp trừu tượng ngắt. Bạn không thực sự cần phải biết điều này. Để thực hiện các biểu thức RT, bạn gọi các hàm thuần túy.
Thuộc tính phái sinh của RT
Bất kỳ thuộc tính nào khác được trích dẫn cho lập trình khai báo, ví dụ trích dẫn từ năm 1999 được Wikipedia sử dụng, xuất phát từ RT hoặc được chia sẻ với lập trình mệnh lệnh. Do đó chứng minh rằng định nghĩa chính xác của tôi là chính xác.
Lưu ý, tính bất biến của các giá trị bên ngoài là tập hợp con của các yêu cầu đối với RT.
Các ngôn ngữ khai báo không có cấu trúc điều khiển vòng lặp, ví dụ for
và while
, do tính không thay đổi , điều kiện vòng lặp sẽ không bao giờ thay đổi.
Các ngôn ngữ khai báo không thể hiện luồng điều khiển ngoài thứ tự hàm lồng nhau (còn gọi là phụ thuộc logic), do tính không thay đổi , các lựa chọn khác của thứ tự đánh giá không thay đổi kết quả (xem bên dưới).
Các ngôn ngữ khai báo biểu thị các "bước" logic (nghĩa là thứ tự gọi hàm RT lồng nhau), nhưng liệu mỗi lệnh gọi hàm có phải là một ngữ nghĩa cấp cao hơn (tức là "phải làm gì") không phải là một yêu cầu của lập trình khai báo. Sự khác biệt so với mệnh lệnh là do tính bất biến (nói chung là RT), các "bước" này không thể phụ thuộc vào trạng thái có thể thay đổi, thay vào đó chỉ là thứ tự quan hệ của logic được biểu thị (tức là thứ tự lồng của các lệnh gọi hàm, hay còn gọi là biểu thức con ).
Ví dụ, đoạn HTML <p>
không thể được hiển thị cho đến khi các biểu thức con (tức là thẻ) trong đoạn văn được đánh giá. Không có trạng thái có thể thay đổi, chỉ có một phụ thuộc thứ tự do mối quan hệ logic của phân cấp thẻ (lồng các biểu thức con, là các lệnh gọi hàm tương tự lồng nhau ).
Do đó, có thuộc tính phái sinh của tính bất biến (nói chung là RT), biểu thức khai báo đó, chỉ biểu thị các mối quan hệ logic của các bộ phận cấu thành (tức là của các đối số hàm biểu thức phụ) và không phải là mối quan hệ trạng thái có thể thay đổi .
Lệnh đánh giá
Việc lựa chọn thứ tự đánh giá của các biểu thức con chỉ có thể cho kết quả khác nhau khi bất kỳ lệnh gọi hàm nào không phải là RT (tức là hàm không thuần túy), ví dụ một số trạng thái có thể thay đổi bên ngoài của hàm được truy cập trong hàm.
Ví dụ, đưa ra một số biểu thức lồng nhau, ví dụ f( g(a, b), h(c, d) )
, đánh giá háo hức và lười biếng của các đối số chức năng sẽ cho kết quả tương tự nếu các chức năng f
, g
và h
là tinh khiết.
Trong khi đó, nếu các hàm f
, g
và h
không thuần túy, thì sự lựa chọn thứ tự đánh giá có thể cho một kết quả khác.
Lưu ý, các biểu thức lồng nhau là các hàm lồng nhau về mặt khái niệm, vì các toán tử biểu thức chỉ là các hàm gọi hàm giả là tiền tố unary, tiền tố unary hoặc ký hiệu infix nhị phân.
Tiếp tuyến, nếu tất cả các định danh, ví dụ như a
, b
, c
, d
, là bất biến ở khắp mọi nơi, tiểu bang bên ngoài để chương trình không thể truy cập (ví dụ: I / O), và không có lớp trừu tượng vỡ, sau đó chức năng luôn tinh khiết.
Nhân tiện, Haskell có một cú pháp khác , f (g a b) (h c d)
.
Chi tiết đánh giá
Hàm là một chuyển trạng thái (không phải là giá trị được lưu trữ có thể thay đổi) từ đầu vào sang đầu ra. Đối với các thành phần RT của các lệnh gọi đến các hàm thuần túy , thứ tự thực hiện của các chuyển đổi trạng thái này là độc lập. Việc chuyển trạng thái của mỗi lệnh gọi hàm là độc lập với các lệnh khác, do thiếu tác dụng phụ và nguyên tắc là hàm RT có thể được thay thế bằng giá trị được lưu trong bộ nhớ cache của nó . Để sửa chữa một quan niệm sai lầm phổ biến , thành phần đơn nguyên thuần túy luôn luôn là tuyên bố và RT , mặc dù thực tế là IO
đơn nguyên của Haskell được cho là không tinh khiết và do đó bắt buộc phải viết World
trạng thái bên ngoài chương trình (nhưng theo nghĩa của cảnh báo bên dưới, tác dụng phụ bị cô lập).
Đánh giá háo hức có nghĩa là các đối số hàm được đánh giá trước khi hàm được gọi và đánh giá lười biếng có nghĩa là các đối số không được đánh giá cho đến khi (và nếu) chúng được truy cập trong hàm.
Định nghĩa : các tham số hàm được khai báo tại trang định nghĩa hàm và các đối số hàm được cung cấp tại vị trí gọi hàm . Biết sự khác biệt giữa tham số và đối số .
Về mặt lý thuyết, tất cả các biểu thức là (a thành phần của) cuộc gọi chức năng, ví dụ như các hằng số được chức năng mà không đầu vào, khai thác unary là chức năng với một đầu vào, khai thác trung tố nhị phân là chức năng với hai đầu vào, nhà thầu là chức năng, và các tuyên bố kiểm soát thậm chí (ví dụ if
, for
, while
) có thể được mô hình hóa với các chức năng. Các thứ tự rằng những lập luận chức năng (Đừng nhầm lẫn với lồng chức năng tự gọi) được đánh giá là không công bố bởi các cú pháp, ví dụ như f( g() )
háo hức có thể đánh giá g
sau đó f
trên g
kết quả 's hoặc nó có thể đánh giá f
và chỉ uể oải đánh giá g
khi kết quả của nó là cần thiết trong phạm vi f
.
Hãy cẩn thận, không có ngôn ngữ hoàn chỉnh Turing (nghĩa là cho phép đệ quy không giới hạn) là hoàn toàn khai báo, ví dụ đánh giá lười biếng giới thiệu bộ nhớ và thời gian không xác định. Nhưng những tác dụng phụ này do sự lựa chọn thứ tự đánh giá bị giới hạn ở mức tiêu thụ bộ nhớ, thời gian thực hiện, độ trễ, không kết thúc và độ trễ ngoài do đó đồng bộ hóa bên ngoài.
Lập trình chức năng
Bởi vì lập trình khai báo không thể có các vòng lặp, nên cách duy nhất để lặp lại là đệ quy hàm. Theo nghĩa này, lập trình chức năng có liên quan đến lập trình khai báo.
Nhưng lập trình chức năng không giới hạn ở lập trình khai báo . Thành phần chức năng có thể tương phản với phân nhóm , đặc biệt là đối với Vấn đề Biểu hiện , trong đó có thể đạt được sự mở rộng bằng cách thêm các phân nhóm hoặc phân tách chức năng . Mở rộng có thể là một kết hợp của cả hai phương pháp.
Lập trình hàm thường làm cho hàm trở thành một đối tượng hạng nhất, nghĩa là kiểu hàm có thể xuất hiện trong ngữ pháp ở bất kỳ nơi nào khác. Kết quả cuối cùng là các hàm có thể nhập và hoạt động trên các hàm, do đó cung cấp sự phân tách mối quan tâm bằng cách nhấn mạnh thành phần hàm, tức là tách các phụ thuộc giữa các tính toán con của một tính toán xác định.
Ví dụ, thay vì viết một hàm riêng biệt (và sử dụng đệ quy thay vì các vòng lặp nếu hàm đó cũng phải là khai báo) cho mỗi số lượng vô hạn các hành động chuyên môn có thể áp dụng cho từng phần tử của bộ sưu tập, lập trình hàm sử dụng phép lặp có thể sử dụng lại chức năng, ví dụ như map
, fold
, filter
. Các hàm lặp này nhập vào một hàm hành động chuyên biệt hạng nhất. Các hàm lặp này lặp lại bộ sưu tập và gọi hàm hành động chuyên biệt đầu vào cho mỗi phần tử. Các hàm hành động này ngắn gọn hơn vì chúng không còn cần chứa các câu lệnh lặp để lặp lại bộ sưu tập.
Tuy nhiên, lưu ý rằng nếu một hàm không thuần túy thì đó thực sự là một thủ tục. Có lẽ chúng ta có thể lập luận rằng lập trình chức năng sử dụng các hàm không tinh khiết, thực sự là lập trình thủ tục. Do đó, nếu chúng ta đồng ý rằng các biểu thức khai báo là RT, thì chúng ta có thể nói rằng lập trình thủ tục không phải là lập trình khai báo, và do đó chúng ta có thể lập luận rằng lập trình hàm luôn là RT và phải là một tập hợp con của lập trình khai báo.
Song song
Thành phần chức năng này với các chức năng hạng nhất có thể biểu thị độ sâu trong sự song song bằng cách tách ra chức năng độc lập.
Nguyên tắc của Brent: tính toán với công việc w và độ sâu d có thể được thực hiện trong PRAM bộ xử lý p trong thời gian O (max (w / p, d)).
Cả đồng thời và song song cũng yêu cầu lập trình khai báo , tức là bất biến và RT.
Vậy giả định nguy hiểm này mà Parallelism == Đồng thời đến từ đâu? Đó là hậu quả tự nhiên của các ngôn ngữ có tác dụng phụ: khi ngôn ngữ của bạn có tác dụng phụ ở mọi nơi, thì bất cứ lúc nào bạn cố gắng làm nhiều việc một lúc, về cơ bản bạn không có tính quyết định gây ra bởi các hiệu ứng xen kẽ từ mỗi thao tác . Vì vậy, trong các ngôn ngữ có hiệu lực phụ, cách duy nhất để có được sự song song là đồng thời; Do đó, không có gì đáng ngạc nhiên khi chúng ta thường thấy hai người bị giam cầm.
Lưu ý thứ tự đánh giá cũng tác động đến tác dụng phụ chấm dứt và hiệu suất của thành phần chức năng.
Háo hức (CBV) và lười biếng (CBN) là các cuộc đấu tay đôi phân loại [ 10 ], bởi vì chúng đã đảo ngược thứ tự đánh giá, tức là liệu các chức năng bên ngoài hay bên trong được đánh giá đầu tiên. Hãy tưởng tượng một cây lộn ngược, sau đó háo hức đánh giá từ các nhánh cây chức năng lên phân cấp nhánh đến thân cây hàm cấp cao nhất; trong khi đó, lười đánh giá từ thân cây đến đầu cành. Eager không có các sản phẩm kết hợp ("và", a / k / a "sản phẩm" phân loại và lười biếng không có các sản phẩm sao chép rời rạc ("hoặc", a / k / a "tổng hợp") [ 11 ].
Hiệu suất
Hăng hái
Cũng như không kết thúc, háo hức quá háo hức với thành phần chức năng kết hợp, tức là cấu trúc điều khiển thành phần thực hiện những công việc không cần thiết không được thực hiện với sự lười biếng. Ví dụ , háo hức và háo hức ánh xạ toàn bộ danh sách thành booleans, khi nó được tạo với một nếp gấp chấm dứt trên phần tử thực đầu tiên.
Công việc không cần thiết này là nguyên nhân của yếu tố "lên đến" được yêu cầu thêm trong độ phức tạp thời gian tuần tự của háo hức so với lười biếng, cả hai đều có chức năng thuần túy. Một giải pháp là sử dụng functor (ví dụ: danh sách) với các hàm tạo lười biếng (nghĩa là háo hức với các sản phẩm lười tùy chọn), bởi vì với sự háo hức, sự háo hức không chính xác bắt nguồn từ chức năng bên trong. Điều này là do các sản phẩm là loại xây dựng, tức là loại quy nạp với đại số ban đầu trên một điểm cố định ban đầu [ 11 ]
Lười biếng
Cũng như không chấm dứt, lười biếng quá lười biếng với thành phần chức năng rời rạc, tức là tính hữu hạn cưỡng chế có thể xảy ra muộn hơn mức cần thiết, dẫn đến cả công việc không cần thiết và không xác định độ trễ không phải là trường hợp háo hức [ 10 ] [ 11 ] . Ví dụ về tính hữu hạn là các ngoại lệ về trạng thái, thời gian, không kết thúc và thời gian chạy. Đây là những tác dụng phụ bắt buộc, nhưng ngay cả trong một ngôn ngữ khai báo thuần túy (ví dụ Haskell), vẫn có trạng thái trong đơn vị IO bắt buộc (lưu ý: không phải tất cả các đơn vị đều bắt buộc!) Ẩn trong phân bổ không gian và thời gian là trạng thái liên quan đến mệnh lệnh thế giới thực. Sử dụng lười biếng ngay cả với các bản sao háo hức tùy chọn rò rỉ "sự lười biếng" vào các bản sao bên trong, bởi vì với sự lười biếng, sự lười biếng không chính xác bắt nguồn từ chức năng bên ngoài(xem ví dụ trong phần Không kết thúc, trong đó == là hàm toán tử nhị phân bên ngoài). Điều này là do các bản sao được giới hạn bởi tính hữu hạn, tức là các kiểu cưỡng chế với đại số cuối cùng trên một đối tượng cuối cùng [ 11 ].
Sự lười biếng gây ra sự không xác định trong thiết kế và gỡ lỗi các chức năng cho độ trễ và không gian, việc gỡ lỗi có lẽ vượt quá khả năng của phần lớn các lập trình viên, vì sự bất đồng giữa hệ thống phân cấp chức năng được khai báo và thứ tự đánh giá thời gian chạy. Các hàm thuần túy lười biếng được đánh giá với sự háo hức, có khả năng có thể giới thiệu không kết thúc chưa từng thấy trước đây trong thời gian chạy. Ngược lại, các hàm thuần túy háo hức được đánh giá với sự lười biếng, có khả năng có thể giới thiệu không gian vô hình và độ trễ không xác định trước đó trong thời gian chạy.
Không chấm dứt
Tại thời điểm biên dịch, do sự cố Dừng và đệ quy lẫn nhau trong một ngôn ngữ hoàn chỉnh Turing, các chức năng thường không thể được đảm bảo để chấm dứt.
Hăng hái
Với sự háo hức nhưng không lười biếng, vì sự kết hợp của Head
"và" Tail
, nếu một trong hai Head
hoặc Tail
không chấm dứt, thì tương ứng List( Head(), Tail() ).tail == Tail()
hoặc List( Head(), Tail() ).head == Head()
là không đúng bởi vì bên trái không, và bên phải không, chấm dứt.
Trong khi đó, với sự lười biếng cả hai bên chấm dứt. Do đó, háo hức quá háo hức với các sản phẩm kết hợp và không kết thúc (bao gồm cả ngoại lệ thời gian chạy) trong những trường hợp không cần thiết.
Lười biếng
Với sự lười biếng nhưng không háo hức, vì sự phân biệt của 1
"hoặc" 2
, nếu f
không chấm dứt, thì điều đó List( f ? 1 : 2, 3 ).tail == (f ? List( 1, 3 ) : List( 2, 3 )).tail
là không đúng bởi vì phía bên trái chấm dứt và bên phải thì không.
Trong khi đó, với sự háo hức không bên nào chấm dứt nên bài kiểm tra bình đẳng không bao giờ đạt được. Do đó, lười biếng là quá lười biếng với các sản phẩm sao chép rời rạc, và trong những trường hợp đó không thể chấm dứt (bao gồm các ngoại lệ thời gian chạy) sau khi thực hiện nhiều công việc hơn háo hức.
[ 10 ] Tiếp tục khai báo và tính đối ngẫu phân loại, Filinski, phần 2.5.4 So sánh CBV và CBN, và 3.6.1 CBV và CBN trong SCL.
[ 11 ] Declarative continuations và Categorical Duality, Filinski, phần 2.2.1 Sản phẩm và coproducts, 2.2.2 Terminal và đối tượng ban đầu, 2.5.2 CBV với các sản phẩm lười biếng, và 2.5.3 CBN với coproducts háo hức.
Thực sự không có bất kỳ định nghĩa khách quan, không mơ hồ cho những điều này. Đây là cách tôi sẽ định nghĩa chúng:
Bắt buộc - Trọng tâm là những gì máy tính nên thực hiện thay vì những gì máy tính sẽ làm (ví dụ: C, C ++, Java).
Tuyên bố - Trọng tâm là những gì máy tính nên làm hơn là cách nó nên làm (ví dụ: SQL).
Chức năng - một tập hợp các ngôn ngữ khai báo tập trung nhiều vào đệ quy
mệnh lệnh và khai báo mô tả hai phong cách lập trình đối lập. bắt buộc là cách tiếp cận "công thức từng bước" truyền thống trong khi khai báo là "đây là điều tôi muốn, bây giờ bạn tìm ra cách thực hiện".
hai cách tiếp cận này xảy ra trong suốt chương trình - ngay cả với cùng một ngôn ngữ và cùng một chương trình. Nói chung, cách tiếp cận khai báo được coi là thích hợp hơn, bởi vì nó giải phóng lập trình viên khỏi việc chỉ định quá nhiều chi tiết, trong khi cũng có ít cơ hội hơn cho các lỗi (nếu bạn mô tả kết quả bạn muốn và một số quy trình tự động được kiểm tra tốt có thể hoạt động ngược từ đó đến xác định các bước sau đó bạn có thể hy vọng rằng mọi thứ đáng tin cậy hơn là phải chỉ định từng bước một).
mặt khác, một cách tiếp cận bắt buộc mang lại cho bạn quyền kiểm soát ở mức độ thấp hơn - đó là "phương pháp vi mô" để lập trình. và điều đó có thể cho phép lập trình viên khai thác kiến thức về vấn đề để đưa ra câu trả lời hiệu quả hơn. do đó, không có gì lạ khi một số phần của chương trình được viết theo phong cách khai báo hơn, nhưng đối với các phần quan trọng về tốc độ thì bắt buộc hơn.
như bạn có thể tưởng tượng, ngôn ngữ bạn sử dụng để viết chương trình ảnh hưởng đến cách bạn có thể khai báo - một ngôn ngữ được tích hợp "thông minh" để tìm ra những gì cần làm được mô tả về kết quả sẽ cho phép khai báo nhiều hơn tiếp cận hơn một nơi mà lập trình viên trước tiên cần thêm loại trí thông minh đó bằng mã bắt buộc trước khi có thể xây dựng một lớp khai báo nhiều hơn trên đầu trang. vì vậy, ví dụ, một ngôn ngữ như prolog được coi là rất khai báo bởi vì nó có, một quá trình tìm kiếm câu trả lời.
cho đến nay, bạn sẽ nhận thấy rằng tôi đã không đề cập đến lập trình chức năng . đó là bởi vì đó là một thuật ngữ mà ý nghĩa của nó không liên quan ngay lập tức đến hai cái kia. đơn giản nhất, lập trình chức năng có nghĩa là bạn sử dụng các chức năng. cụ thể là bạn sử dụng ngôn ngữ hỗ trợ các hàm làm "giá trị hạng nhất" - điều đó có nghĩa là bạn không chỉ có thể viết các hàm mà còn có thể viết các hàm viết các hàm (viết các hàm đó ...) và truyền các hàm cho chức năng. Nói tóm lại - các hàm đó linh hoạt và phổ biến như những thứ như chuỗi và số.
Sau đó, nó có vẻ kỳ lạ, rằng chức năng, mệnh lệnh và khai báo thường được đề cập cùng nhau. lý do cho điều này là một hệ quả của việc đưa ý tưởng về lập trình chức năng "đến mức cực đoan". một hàm, theo nghĩa thuần túy nhất của nó, là một thứ gì đó từ toán học - một loại "hộp đen" lấy một số đầu vào và luôn cho cùng một đầu ra. và loại hành vi đó không yêu cầu lưu trữ các biến thay đổi. Vì vậy, nếu bạn thiết kế một ngôn ngữ lập trình với mục đích thực hiện một ý tưởng rất thuần túy, chịu ảnh hưởng toán học của lập trình chức năng, thì cuối cùng bạn sẽ từ chối, phần lớn, ý tưởng về các giá trị có thể thay đổi (theo một nghĩa nhất định, hạn chế, kỹ thuật).
và nếu bạn làm điều đó - nếu bạn giới hạn cách các biến có thể thay đổi - thì gần như tình cờ bạn sẽ buộc lập trình viên phải viết các chương trình mang tính khai báo nhiều hơn, bởi vì một phần lớn của lập trình mệnh lệnh là mô tả cách các biến thay đổi và bạn không còn có thể làm điều đó! Vì vậy, nó chỉ ra rằng lập trình chức năng - đặc biệt, lập trình bằng ngôn ngữ chức năng - có xu hướng đưa ra nhiều mã khai báo hơn.
để tóm tắt, sau đó:
mệnh lệnh và khai báo là hai phong cách lập trình đối lập nhau (cùng tên được sử dụng cho các ngôn ngữ lập trình khuyến khích các phong cách đó)
lập trình chức năng là một phong cách lập trình trong đó các chức năng trở nên rất quan trọng và do đó, việc thay đổi các giá trị trở nên ít quan trọng hơn. khả năng hạn chế để xác định các thay đổi trong các giá trị buộc một kiểu khai báo hơn.
vì vậy "lập trình chức năng" thường được mô tả là "khai báo".
Tóm lại:
Một ngôn ngữ bắt buộc chỉ định một loạt các hướng dẫn mà máy tính thực hiện theo trình tự (làm điều này, sau đó làm điều đó).
Một ngôn ngữ khai báo khai báo một tập hợp các quy tắc về kết quả đầu ra nào sẽ xuất phát từ đầu vào nào (ví dụ: nếu bạn có A, thì kết quả là B). Một động cơ sẽ áp dụng các quy tắc này cho đầu vào và đưa ra đầu ra.
Một ngôn ngữ chức năng khai báo một tập hợp các hàm toán học / logic xác định cách dịch đầu vào thành đầu ra. ví dụ. f (y) = y * y. nó là một loại ngôn ngữ khai báo.
Bắt buộc: làm thế nào để đạt được mục tiêu của chúng tôi
Take the next customer from a list.
If the customer lives in Spain, show their details.
If there are more customers in the list, go to the beginning
Tuyên bố: những gì chúng ta muốn đạt được
Show customer details of every customer living in Spain
Lập trình mệnh lệnh có nghĩa là bất kỳ phong cách lập trình nào trong đó chương trình của bạn được cấu trúc theo các hướng dẫn mô tả cách các hoạt động được thực hiện bởi máy tính sẽ xảy ra .
Lập trình khai báo có nghĩa là bất kỳ phong cách lập trình nào trong đó chương trình của bạn là một mô tả về vấn đề hoặc giải pháp - nhưng không nói rõ cách thức công việc sẽ được thực hiện .
Lập trình hàm là lập trình bằng cách đánh giá các hàm và hàm của hàm ... Vì lập trình hàm (được định nghĩa nghiêm ngặt) có nghĩa là lập trình bằng cách định nghĩa các hàm toán học miễn phí có hiệu lực phụ nên nó là một dạng lập trình khai báo nhưng nó không phải là loại lập trình khai báo duy nhất .
Lập trình logic (ví dụ trong Prolog) là một dạng lập trình khai báo khác. Nó liên quan đến tính toán bằng cách quyết định xem một tuyên bố logic có đúng không (hoặc liệu nó có thể được thỏa mãn hay không). Chương trình này thường là một chuỗi các sự kiện và quy tắc - tức là một mô tả chứ không phải là một loạt các hướng dẫn.
Viết lại thuật ngữ (ví dụ CASL) là một hình thức lập trình khai báo khác. Nó liên quan đến chuyển đổi biểu tượng của các thuật ngữ đại số. Nó hoàn toàn khác biệt với lập trình logic và lập trình chức năng.
mệnh lệnh - biểu thức mô tả chuỗi hành động cần thực hiện (kết hợp)
khai báo - biểu thức là các khai báo đóng góp vào hành vi của chương trình (kết hợp, giao hoán, bình dị, đơn điệu)
chức năng - biểu thức có giá trị như hiệu ứng duy nhất; ngữ nghĩa hỗ trợ lý luận phương trình
Vì tôi đã viết câu trả lời trước của mình, tôi đã đưa ra một định nghĩa mới về tài sản khai báo được trích dẫn dưới đây. Tôi cũng đã định nghĩa lập trình mệnh lệnh là thuộc tính kép.
Định nghĩa này là tốt hơn so với câu tôi đã cung cấp trong câu trả lời trước của tôi, bởi vì nó ngắn gọn và nó khái quát hơn. Nhưng nó có thể khó khăn hơn để mò mẫm, bởi vì hàm ý của các định lý không hoàn chỉnh áp dụng cho lập trình và cuộc sống nói chung rất khó để con người quấn lấy tâm trí của họ.
Các giải thích được trích dẫn của định nghĩa thảo luận về vai trò của lập trình chức năng thuần túy trong lập trình khai báo.
Tất cả các loại lập trình kỳ lạ phù hợp với phân loại sau đây của khai báo so với mệnh lệnh, vì định nghĩa sau đây khẳng định chúng là đối ngẫu.
Tuyên bố so với mệnh lệnh
Thuộc tính khai báo là kỳ lạ, khó hiểu và khó nắm bắt trong một định nghĩa chính xác về mặt kỹ thuật vẫn còn chung chung và không mơ hồ, bởi vì đó là một khái niệm ngây thơ rằng chúng ta có thể tuyên bố ý nghĩa (còn gọi là ngữ nghĩa) của chương trình mà không gây ra tác dụng phụ ngoài ý muốn. Có một sự căng thẳng cố hữu giữa việc thể hiện ý nghĩa và tránh các tác động ngoài ý muốn, và sự căng thẳng này thực sự xuất phát từ các định lý không hoàn chỉnh của lập trình và vũ trụ của chúng ta.
Đó là sự đơn giản hóa, về mặt kỹ thuật không chính xác, và thường không rõ ràng để xác định tường thuật như “ phải làm gì ” và bắt buộc như “ làm thế nào để làm ” . Một trường hợp không rõ ràng là người Viking, người là người thế nào trong một chương trình tạo ra một chương trình, một trình biên dịch.
Rõ ràng là đệ quy không giới hạn làm cho một ngôn ngữ Turing hoàn chỉnh , cũng tương tự trong ngữ nghĩa, không chỉ trong cấu trúc cú pháp đánh giá (còn gọi là ngữ nghĩa hoạt động). Đây là một ví dụ về mặt logic tương tự như định lý của Gôdel, bất kỳ hệ thống tiên đề hoàn chỉnh nào cũng không nhất quán . Suy ngẫm về sự kỳ lạ mâu thuẫn của trích dẫn đó! Nó cũng là một ví dụ chứng minh làm thế nào biểu thức ngữ nghĩa không có ràng buộc có thể chứng minh được, do đó chúng ta không thể chứng minh 2 rằng một chương trình (và tương tự như ngữ nghĩa của nó) dừng lại là định lý Dừng.
Các định lý không hoàn chỉnh xuất phát từ bản chất cơ bản của vũ trụ của chúng ta, như đã nêu trong Định luật Nhiệt động lực học thứ hai là entropy (hay còn gọi là # khả năng độc lập) đang có xu hướng tối đa mãi mãi . Việc mã hóa và thiết kế chương trình không bao giờ kết thúc được. Nó còn sống! - bởi vì nó cố gắng giải quyết nhu cầu của thế giới thực, và ngữ nghĩa của thế giới thực luôn thay đổi và có xu hướng nhiều khả năng hơn. Con người không bao giờ ngừng khám phá những điều mới (bao gồm cả lỗi trong các chương trình ;-).
Để nắm bắt chính xác và kỹ thuật khái niệm mong muốn đã nói ở trên trong vũ trụ kỳ lạ không có lợi thế này sâu sắc.
Định nghĩa:
Thuộc tính khai báo là nơi chỉ có thể tồn tại một tập hợp các câu lệnh có thể biểu thị từng ngữ nghĩa mô-đun cụ thể.
Thuộc tính mệnh lệnh 3 là thuộc tính kép, trong đó ngữ nghĩa không nhất quán theo thành phần và / hoặc có thể được thể hiện bằng các biến thể của các bộ câu lệnh.
Định nghĩa khai báo này đặc biệt cục bộ trong phạm vi ngữ nghĩa, có nghĩa là nó yêu cầu một ngữ nghĩa mô-đun duy trì ý nghĩa nhất quán của nó bất kể nó được khởi tạo và sử dụng trong phạm vi toàn cầu ở đâu và như thế nào . Do đó, mỗi ngữ nghĩa mô-đun khai báo phải trực giao với tất cả những người khác có thể khác và không phải là một thuật toán toàn cầu (do các định lý không hoàn hảo) để chứng kiến tính nhất quán, cũng là điểm của Thay vì không phải lúc nào cũng tốt hơn bởi Robert Harper, Giáo sư Khoa học máy tính tại Đại học Carnegie Mellon, một trong những nhà thiết kế của Standard ML.
Ví dụ về các ngữ nghĩa tường thuật mô-đun bao gồm loại functors lý thuyết, ví dụ như các
Applicative
, gõ danh nghĩa, không gian tên, tên trường này, và wrt để cấp độ hoạt động của ngữ nghĩa sau đó lập trình chức năng thuần túy.Do đó, các ngôn ngữ khai báo được thiết kế tốt có thể diễn đạt rõ ràng hơn ý nghĩa , mặc dù có một số mất tính tổng quát trong những gì có thể diễn đạt, nhưng đạt được những gì có thể được thể hiện với tính nhất quán nội tại.
Một ví dụ về định nghĩa đã nói ở trên là tập hợp các công thức trong các ô của chương trình bảng tính mà không mong đợi sẽ có cùng ý nghĩa khi được chuyển đến các ô cột và hàng khác nhau, tức là các định danh ô đã thay đổi. Các định danh ô là một phần và không thừa đối với ý nghĩa dự định. Vì vậy, mỗi kết quả bảng tính là wrt duy nhất cho các định danh ô trong một tập hợp các công thức. Ngữ nghĩa mô đun nhất quán trong trường hợp này là sử dụng các định danh ô làm đầu vào và đầu ra của các hàm thuần cho các công thức ô (xem bên dưới).
Ngôn ngữ đánh dấu siêu văn bản hay còn gọi là HTML. Ngôn ngữ cho các trang web tĩnh là một ví dụ về ngôn ngữ khai báo cao (nhưng không hoàn hảo 3 ) mà (ít nhất là trước HTML 5) không có khả năng diễn đạt hành vi động. HTML có lẽ là ngôn ngữ dễ học nhất. Đối với hành vi động, một ngôn ngữ kịch bản bắt buộc như JavaScript thường được kết hợp với HTML. HTML không có JavaScript phù hợp với định nghĩa khai báo vì mỗi loại danh nghĩa (nghĩa là các thẻ) duy trì ý nghĩa nhất quán của nó theo thành phần trong các quy tắc của cú pháp.
Một định nghĩa cạnh tranh đối với khai báo là giao hoán và idempotent tính chất của những điều khoản về ngữ nghĩa, tức là giấy chi tiết có thể được sắp xếp lại và lặp lại mà không thay đổi ý nghĩa. Ví dụ: các câu lệnh gán giá trị cho các trường được đặt tên có thể được sắp xếp lại và sao chép mà không thay đổi ý nghĩa của chương trình, nếu các tên đó được mô đun hóa theo bất kỳ thứ tự ngụ ý nào. Các tên đôi khi ngụ ý một đơn đặt hàng, ví dụ như số nhận dạng ô bao gồm vị trí cột và hàng của chúng, việc di chuyển tổng số trên bảng tính sẽ thay đổi ý nghĩa của nó. Mặt khác, các thuộc tính này hoàn toàn yêu cầu toàn cầutính nhất quán của ngữ nghĩa. Nhìn chung, không thể thiết kế ngữ nghĩa của các câu lệnh sao cho chúng vẫn nhất quán nếu được sắp xếp ngẫu nhiên hoặc trùng lặp, bởi vì thứ tự và trùng lặp là nội tại đối với ngữ nghĩa. Ví dụ, các câu lệnh Foo Foo tồn tại (và xây dựng) và Foo Foo không tồn tại (và phá hủy). Nếu người ta xem xét sự không nhất quán ngẫu nhiên về mặt hóa học của ngữ nghĩa dự định, thì người ta chấp nhận định nghĩa này là đủ chung cho thuộc tính khai báo. Về bản chất, định nghĩa này là không rõ ràng như một định nghĩa tổng quát vì nó cố gắng tạo sự nhất quán trực giao với ngữ nghĩa, nghĩa là thách thức thực tế rằng vũ trụ của ngữ nghĩa là không bị ràng buộc và không thể bị bắt trong một mô hình kết hợp toàn cầu .
Yêu cầu các đặc tính giao hoán và không cần thiết cho (thứ tự đánh giá cấu trúc của) ngữ nghĩa hoạt động cấp thấp hơn chuyển đổi ngữ nghĩa hoạt động thành một ngữ nghĩa mô đun cục bộ khai báo , ví dụ lập trình hàm thuần túy (bao gồm cả đệ quy thay vì các vòng lặp bắt buộc). Sau đó, thứ tự hoạt động của các chi tiết thực hiện không ảnh hưởng (tức là lan rộng ra toàn cầu ) tính nhất quán của ngữ nghĩa cấp cao hơn. Ví dụ, thứ tự đánh giá (và về mặt lý thuyết cũng là sự trùng lặp) của các công thức bảng tính không thành vấn đề vì các đầu ra không được sao chép vào các đầu vào cho đến khi tất cả các đầu ra đã được tính toán, nghĩa là tương tự với các hàm thuần túy.
C, Java, C ++, C #, PHP và JavaScript không đặc biệt khai báo. Cú pháp của Copute và cú pháp của Python được kết hợp nhiều hơn với các kết quả dự định , nghĩa là ngữ nghĩa cú pháp nhất quán loại bỏ tính không liên quan để người ta có thể dễ dàng hiểu được mã sau khi họ quên nó. Copute và Haskell thi hành tính xác định của ngữ nghĩa hoạt động và khuyến khích Cameron không lặp lại chính mình (DRY), bởi vì chúng chỉ cho phép mô hình chức năng thuần túy.
2 Ngay cả khi chúng ta có thể chứng minh ngữ nghĩa của một chương trình, ví dụ với ngôn ngữ Coq, điều này bị giới hạn trong ngữ nghĩa được thể hiện trong cách gõ và việc gõ không bao giờ có thể nắm bắt được tất cả các ngữ nghĩa của chương trình, ngay cả đối với các ngôn ngữ chưa hoàn thành Turing, ví dụ với HTML + CSS, có thể biểu thị các kết hợp không nhất quán do đó có ngữ nghĩa không xác định.
3 Nhiều giải thích không chính xác cho rằng chỉ có lập trình mệnh lệnh mới có các câu lệnh được tổng hợp. Tôi đã làm rõ sự nhầm lẫn này giữa lập trình mệnh lệnh và chức năng . Ví dụ, thứ tự của các câu lệnh HTML không làm giảm tính nhất quán về nghĩa của chúng.
Chỉnh sửa: Tôi đã đăng bình luận sau đây lên blog của Robert Harper:
trong lập trình chức năng ... phạm vi biến thể của một biến là một loại
Tùy thuộc vào cách người ta phân biệt chức năng với lập trình mệnh lệnh, 'khả năng được gán' của bạn trong một chương trình bắt buộc cũng có thể có một loại đặt ràng buộc vào tính biến thiên của nó.
Định nghĩa không bị nhầm lẫn duy nhất mà tôi hiện đánh giá cao cho lập trình chức năng là a) các hàm như các đối tượng và loại hạng nhất, b) ưu tiên cho đệ quy trên các vòng lặp và / hoặc c) các hàm thuần túy tức là các hàm đó không ảnh hưởng đến ngữ nghĩa mong muốn của chương trình khi được ghi nhớ ( do đó lập trình chức năng hoàn toàn thuần túy không tồn tại trong ngữ nghĩa biểu thị mục đích chung do tác động của ngữ nghĩa hoạt động, ví dụ phân bổ bộ nhớ ).
Thuộc tính idempotent của một hàm thuần có nghĩa là hàm gọi trên các biến của nó có thể được thay thế bằng giá trị của nó, thường không phải là trường hợp cho các đối số của một thủ tục bắt buộc. Các hàm thuần túy dường như là wrt khai báo cho các chuyển trạng thái không tách rời giữa các kiểu đầu vào và kết quả.
Nhưng thành phần của các hàm thuần túy không duy trì bất kỳ tính nhất quán nào như vậy, bởi vì có thể mô hình hóa một quy trình bắt buộc tác dụng phụ (trạng thái toàn cầu) bằng ngôn ngữ lập trình hàm thuần túy, ví dụ IOMonad của Haskell và hơn nữa, hoàn toàn không thể ngăn chặn việc đó bất kỳ Turing hoàn thành ngôn ngữ lập trình chức năng thuần túy.
Như tôi đã viết vào năm 2012 có vẻ như có sự đồng thuận tương tự của các bình luận trong blog gần đây của bạn , rằng lập trình khai báo là một nỗ lực để nắm bắt khái niệm rằng ngữ nghĩa dự định không bao giờ mờ đục. Ví dụ về ngữ nghĩa mờ là sự phụ thuộc vào thứ tự, sự phụ thuộc vào việc xóa ngữ nghĩa cấp cao hơn ở lớp ngữ nghĩa hoạt động (ví dụ: phôi không phải là chuyển đổi và tổng quát giới hạn ngữ nghĩa cấp cao hơn ) và không thể kiểm tra được các giá trị biến đổi đúng) bởi ngôn ngữ lập trình.
Vì vậy, tôi đã kết luận rằng chỉ những ngôn ngữ hoàn chỉnh không Turing mới có thể được khai báo.
Do đó, một thuộc tính rõ ràng và khác biệt của ngôn ngữ khai báo có thể là đầu ra của nó có thể được chứng minh là tuân theo một số quy tắc tổng quát. Ví dụ, đối với bất kỳ chương trình HTML cụ thể nào (bỏ qua sự khác biệt trong cách phân tích trình thông dịch) không được viết theo kịch bản (nghĩa là không hoàn thành Turing) thì có thể đếm được biến thiên đầu ra của nó. Hay ngắn gọn hơn, một chương trình HTML là một chức năng thuần túy của tính biến đổi của nó. Ditto một chương trình bảng tính là một hàm thuần túy của các biến đầu vào của nó.
Vì vậy, dường như đối với tôi, các ngôn ngữ khai báo là phản đề của đệ quy không giới hạn , nghĩa là các định lý không hoàn chỉnh thứ hai của Gôdel không thể được chứng minh.
Lesie Lamport đã viết một câu chuyện cổ tích về cách Euclid có thể đã làm việc xung quanh các định lý không hoàn chỉnh của Gôdel áp dụng cho các bằng chứng toán học trong bối cảnh ngôn ngữ lập trình bằng cách kết hợp giữa các loại và logic (tương ứng Curry-Howard, v.v.).
Lập trình bắt buộc: nói với máy máy trực tuyến, cách làm một cái gì đó và kết quả là những gì bạn muốn xảy ra sẽ xảy ra.
Lập trình khai báo: nói với máy trên máy tính mạng những gì bạn muốn xảy ra và để máy tính tìm ra cách thực hiện.
function makeWidget(options) {
const element = document.createElement('div');
element.style.backgroundColor = options.bgColor;
element.style.width = options.width;
element.style.height = options.height;
element.textContent = options.txt;
return element;
}
function makeWidget(type, txt) {
return new Element(type, txt);
}
Lưu ý: Sự khác biệt không phải là một trong những điều ngắn gọn hay phức tạp hay trừu tượng. Như đã nêu, sự khác biệt là làm thế nào so với những gì .
Các khía cạnh Bắt buộc / Tuyên bố / Chức năng trước đây rất tốt để phân loại các ngôn ngữ chung, nhưng hiện nay tất cả "ngôn ngữ lớn" (như Java, Python, Javascript, v.v.) đều có một số tùy chọn (điển hình là khung ) để diễn đạt với "trọng tâm khác" hơn cái chính của nó (mệnh lệnh thông thường) và để diễn tả các quá trình song song, các hàm khai báo, lambdas, v.v.
Vì vậy, một biến thể tốt của câu hỏi này là "khía cạnh nào là tốt để phân loại các khung ngày nay?" ... Một khía cạnh quan trọng là thứ mà chúng ta có thể gắn nhãn "phong cách lập trình" ...
Một ví dụ tốt để giải thích. Như bạn có thể đọc về jQuery tại Wikipedia ,
Tập hợp các tính năng cốt lõi của jQuery - lựa chọn, truyền tải và thao tác phần tử DOM -, được kích hoạt bởi công cụ chọn của nó (...), tạo ra một "kiểu lập trình" mới, hợp nhất các thuật toán và cấu trúc dữ liệu DOM
Vì vậy, jQuery là tốt nhất (phổ biến) ví dụ về tập trung vào một "phong cách lập trình mới" , có nghĩa là không chỉ định hướng đối tượng, là " keo thuật toán và dữ liệu cấu trúc ". jQuery có phần phản ứng như bảng tính, nhưng không phải là "hướng theo ô", là " định hướng nút DOM " ... So sánh các kiểu chính trong ngữ cảnh này:
Không hợp nhất : trong tất cả các "ngôn ngữ lớn", trong bất kỳ biểu thức Chức năng / Tuyên bố / mệnh lệnh nào, thông thường là "không hợp nhất" dữ liệu và thuật toán, ngoại trừ theo một số hướng đối tượng, đó là sự hợp nhất trong quan điểm cấu trúc xoay vòng nghiêm ngặt .
Một số hợp nhất : tất cả các chiến lược hợp nhất cổ điển, ngày nay có một số khung sử dụng nó như mô hình ... dataflow , lập trình hướng sự kiện (hoặc các ngôn ngữ cụ thể của miền cũ như awk và XSLT ) ... Giống như lập trình với bảng tính hiện đại, chúng cũng ví dụ về phong cách lập trình phản ứng .
Hợp nhất lớn : là "phong cách jQuery" ... jQuery là ngôn ngữ cụ thể của miền tập trung vào " thuật toán hợp nhất và cấu trúc dữ liệu DOM ".
PS: các "ngôn ngữ truy vấn" khác, như XQuery, SQL (với PL là tùy chọn biểu thức mệnh lệnh) cũng là các ví dụ hợp nhất thuật toán dữ liệu, nhưng chúng là các đảo , không hợp nhất với các mô-đun hệ thống khác ... Mùa xuân , khi sử dụng find()
-variants và Điều khoản đặc tả , là một ví dụ tổng hợp tốt khác.
Lập trình khai báo là lập trình bằng cách biểu thị một số logic vượt thời gian giữa đầu vào và đầu ra, ví dụ, trong mã giả, ví dụ sau đây sẽ là khai báo:
def factorial(n):
if n < 2:
return 1
else:
return factorial(n-1)
output = factorial(argvec[0])
Chúng tôi chỉ định nghĩa một mối quan hệ gọi là 'giai thừa' ở đây và xác định mối quan hệ giữa đầu ra và đầu vào là mối quan hệ đó. Như là điều hiển nhiên ở đây, về bất kỳ ngôn ngữ có cấu trúc nào cũng cho phép lập trình khai báo được mở rộng. Một ý tưởng trung tâm của lập trình khai báo là dữ liệu bất biến, nếu bạn gán cho một biến, bạn chỉ làm như vậy một lần, và sau đó không bao giờ lặp lại. Các định nghĩa khác, chặt chẽ hơn đòi hỏi rằng có thể không có tác dụng phụ nào cả, những ngôn ngữ này đôi khi được gọi là 'hoàn toàn khai báo'.
Kết quả tương tự trong một phong cách bắt buộc sẽ là:
a = 1
b = argvec[0]
while(b < 2):
a * b--
output = a
Trong ví dụ này, chúng tôi thể hiện không có mối quan hệ logic tĩnh vượt thời gian giữa đầu vào và đầu ra, chúng tôi đã thay đổi địa chỉ bộ nhớ theo cách thủ công cho đến khi một trong số chúng giữ kết quả mong muốn. Rõ ràng là tất cả các ngôn ngữ cho phép một số ngữ nghĩa khai báo được mở rộng, nhưng không phải tất cả các ngôn ngữ cho phép bắt buộc, một số ngôn ngữ khai báo 'hoàn toàn' cho phép tác dụng phụ và đột biến hoàn toàn.
Các ngôn ngữ khai báo thường được cho là chỉ định 'những gì phải được thực hiện', trái ngược với 'cách thực hiện', tôi nghĩ đó là một cách viết sai, các chương trình khai báo vẫn chỉ định cách người ta phải nhận từ đầu vào đến đầu ra, nhưng theo một cách khác, mối quan hệ bạn chỉ định phải được tính toán hiệu quả (thuật ngữ quan trọng, hãy tìm kiếm nếu bạn không biết). Một cách tiếp cận khác là lập trình không điều kiện, thực sự chỉ xác định những điều kiện mà kết quả đáp ứng nhiều, trước khi việc triển khai của bạn chỉ làm cạn kiệt tất cả các đường dẫn về thử nghiệm và lỗi cho đến khi thành công.
Các ngôn ngữ khai báo thuần túy bao gồm Haskell và Pure Prolog. Một thang trượt từ cái này sang cái kia sẽ là: Pure Prolog, Haskell, OCaml, Scheme / Lisp, Python, Javascript, C--, Perl, PHP, C ++, Pascall, C, Fortran, hội
factorial
không làm thay đổi bất kỳ giá trị nào.
Một số câu trả lời tốt ở đây liên quan đến "các loại" được ghi chú.
Tôi gửi một số khái niệm bổ sung, "kỳ lạ" hơn thường được liên kết với đám đông lập trình chức năng:
Tôi nghĩ rằng phân loại của bạn là không chính xác. Có hai loại mệnh lệnh ngược và khai báo. Chức năng chỉ là một kiểu con của khai báo. BTW, wikipedia nêu thực tế tương tự.
Tóm lại, một phong cách lập trình càng nhấn mạnh những gì (phải làm) trừu tượng hóa các chi tiết của Làm thế nào (để làm điều đó) thì phong cách đó càng được coi là khai báo. Điều ngược lại là đúng đối với mệnh lệnh. Lập trình chức năng được liên kết với phong cách khai báo.