Làm thế nào là ngôn ngữ mệnh lệnh khác nhau hơn so với ngôn ngữ chức năng?


17

Tôi đang đọc cuốn Thực thi ngôn ngữ lập trình chức năng của Simon Peyton Jones và có một câu khiến tôi hơi ngạc nhiên (ở trang 39):

Ở một mức độ lớn hơn nhiều so với trường hợp của các ngôn ngữ mệnh lệnh, các ngôn ngữ chức năng chủ yếu là các biến thể cú pháp của nhau, với tương đối ít khác biệt về ngữ nghĩa.

Bây giờ, điều này đã được viết vào năm 1987 và suy nghĩ của tôi về chủ đề này có thể bị ảnh hưởng bởi các ngôn ngữ lập trình hiện đại hơn không phổ biến hoặc phổ biến sau đó. Tuy nhiên, tôi thấy điều này hơi khó tin. Chẳng hạn, tôi nghĩ rằng ngôn ngữ lập trình Miranda được mô tả (tiền thân của Haskell) có ngữ nghĩa khác biệt hơn nhiều so với một ngôn ngữ khắt khe như ML so với C nói với Pascal hoặc thậm chí C phải nói nhỏ hơn (mặc dù tôi sẽ nhượng bộ C ++ cung cấp một số xác nhận quan điểm của mình :-).

Nhưng một lần nữa, tôi dựa trên sự hiểu biết trực quan của mình. Là Simon Peyton Jones phần lớn đúng khi nói điều này, hay đây là một điểm gây tranh cãi?

Câu trả lời:


19

Simon về cơ bản là chính xác, từ quan điểm mở rộng. Chúng ta biết khá rõ ngữ nghĩa của các ngôn ngữ chức năng hiện đại là gì và chúng thực sự là những biến thể tương đối nhỏ của nhau - mỗi ngôn ngữ đại diện cho các bản dịch hơi khác nhau thành một ngôn ngữ kim loại đơn âm. Ngay cả một ngôn ngữ như Scheme (một ngôn ngữ mệnh lệnh bậc cao được gõ động với điều khiển hạng nhất) cũng có một ngữ nghĩa khá gần với ML và Haskell.

Từ quan điểm của denotational xem, bạn có thể bắt đầu bằng cách đưa ra một phương trình miền khá đơn giản đối với ngữ nghĩa của Đề án - gọi nó là . Mọi người có thể và đã giải các phương trình như thế này vào cuối thập niên 70 / đầu thập niên 80, vì vậy điều này không quá tệ. Tương tự, có những ngữ nghĩa hoạt động tương đối đơn giản cho Scheme. (Lưu ý rằng khi tôi nói "Lược đồ", ý tôi là phép tính lambda chưa được cộng với trạng thái tiếp diễn cộng với trạng thái, trái ngược với Đề án thực tế có một vài mụn cóc như tất cả các ngôn ngữ thực sự làm.)V

Nhưng để có được một thể loại phù hợp để diễn giải các ngôn ngữ chức năng được gõ hiện đại, mọi thứ trở nên khá đáng sợ. Về cơ bản, bạn kết thúc việc xây dựng một thể loại làm giàu siêu ma trận về các mối quan hệ tương đương một phần trên miền này. (Ví dụ, xem "Ngữ nghĩa thực tế của đa hình tham số, tham chiếu chung và các loại đệ quy" của Birkedal, Stovring và Thamsborg.). Những người thích ngữ nghĩa hoạt động đều biết công cụ này là quan hệ logic được lập chỉ mục từng bước. (Ví dụ, xem "Độc lập đại diện phụ thuộc nhà nước" của Ahmed, Dreyer và Rossberg.) Dù sao đi nữa, các kỹ thuật được sử dụng là tương đối mới.

Lý do cho sự phức tạp toán học này là chúng ta cần có khả năng giải thích đa hình tham số và trạng thái bậc cao hơn cùng một lúc. Nhưng một khi bạn đã làm điều này, về cơ bản bạn không có nhà, vì công trình này chứa tất cả các bit cứng. Bây giờ, bạn có thể giải thích các loại ML và Haskell thông qua các bản dịch đơn âm thông thường. Không gian chức năng nghiêm ngặt, hiệu quả của ML a -> bchuyển thành <a \ right> và không gian chức năng lười biếng của Haskell chuyển thành <a \ right> , với loại tác dụng phụ đơn nguyên diễn giải đơn vị IO của Haskell và là cách hiểu của loại ML hoặc Haskell vàmột b T ( Một ) một mộtTbmộtbT(Một)mộta là số mũ trong danh mục PER đó.

Theo như lý thuyết phương trình, vì cả hai ngôn ngữ này đều có thể được mô tả bằng các bản dịch thành các tập con hơi khác nhau của cùng một ngôn ngữ, nên hoàn toàn công bằng khi gọi chúng là các biến thể cú pháp của nhau.

Sự khác biệt về cảm nhận giữa ML và Haskell thực sự phát sinh từ các thuộc tính cường độ của hai ngôn ngữ - đó là thời gian thực hiện và mức tiêu thụ bộ nhớ. ML có một mô hình hiệu suất thành phần (nghĩa là chi phí thời gian / không gian của chương trình có thể được tính từ chi phí thời gian / không gian của các bộ con của nó), giống như một ngôn ngữ gọi bằng tên thật. Haskell thực tế được triển khai với nhu cầu gọi, một loại ghi nhớ và kết quả là hiệu suất của nó không phải là thành phần - thời gian một biểu thức bị ràng buộc với một biến sẽ đánh giá tùy thuộc vào việc nó đã được sử dụng trước đó hay chưa. Điều này không được mô hình hóa trong ngữ nghĩa mà tôi đã đề cập ở trên.

Nếu bạn muốn thực hiện các thuộc tính nghiêm túc hơn, thì ML và Haskell bắt đầu cho thấy sự khác biệt nghiêm trọng hơn. Có lẽ vẫn có thể nghĩ ra một ngôn ngữ kim loại phổ biến cho họ, nhưng việc giải thích các loại sẽ khác nhau theo một cách có hệ thống hơn nhiều, liên quan đến ý tưởng lý thuyết chứng minh về sự tập trung . Một nơi tốt để tìm hiểu về điều này là luận án tiến sĩ của Noam Zeilberger.


10

Ý thức của tôi là SPJ đã đề cập đến các ngôn ngữ hoàn toàn chức năng - tức là các ngôn ngữ được minh bạch tham chiếu. Điều này bao gồm, ví dụ, Haskell, Miranda, Clean, nhưng không phải ML. Một khi bạn có một ngôn ngữ chức năng thuần túy, nói chung, bạn có thể cung cấp cho nó một ngữ nghĩa biểu thị khá rõ ràng và được xác định rõ ràng. Nói chung, ngữ nghĩa này sẽ trông giống như một tính toán cho lambda, với một số điều chỉnh ở đây và ở đó. Nói chung, bạn sẽ có một hệ thống loại tương tự như một biến thể của Hệ thống F - có lẽ mạnh hơn ở một số khía cạnh, bị hạn chế hơn ở những người khác. Đây là lý do tại sao việc trích xuất mã / biên dịch sang Haskell, O'Caml, v.v ... tương đối đơn giản từ các trợ lý chứng minh được gõ phụ thuộc tinh vi như Agda.

Trong khuôn khổ đó, có rất nhiều chỗ để chơi. Chắc chắn, vẫn có một sự khác biệt giữa một ngôn ngữ không nghiêm ngặt và nghiêm ngặt. Tuy nhiên, trong trường hợp không có tác dụng phụ, sự khác biệt duy nhất là một ngôn ngữ không nghiêm ngặt chứa nhiều biểu thức không biểu thị đáy - trong chừng mực vì hai chiến lược đánh giá đều không mang lại kết quả cuối cùng, họ đồng ý.

Tuyên bố của Simon cũng phù hợp với bối cảnh lịch sử rất quan trọng. Vào thời điểm Haskell (1987) ra đời, có rất nhiều ngôn ngữ chức năng không nghiêm ngặt - không chỉ Miranda, mà cả Lazy ML, Orwell, Clean, và nhiều ngôn ngữ khác. Ngoài các biến thể cú pháp nhất định, tất cả chúng đều có cùng một ngôn ngữ. Đó chính xác là động lực để Ủy ban Haskell thành lập. Để biết thêm về điều này, hãy xem "Lịch sử của Haskell: lười biếng với lớp học": http://research.microsoft.com/en-us/um/people/simonpj/ con / history-of- haskell / .


5

Tôi nghĩ SPJ là chính xác khi nói điều này cho ngữ nghĩa cốt lõi.

Mặc dù có nhiều điểm tinh tế nâng cao mà người ta có thể chỉ ra, như mặc định cho đánh giá nghiêm ngặt hoặc lười biếng, như bạn đã đề cập, các chi tiết về hệ thống loại hoặc cách tổ chức các đơn vị mã lớn hơn (mô-đun, cấu trúc), mô hình tinh thần của chương trình rất giống nhau trên các ngôn ngữ chức năng.

Chọn một số chức năng được chỉ định cụ thể và viết nó bằng tất cả các ngôn ngữ mà bạn đang so sánh, và bạn có thể sẽ thấy rằng cấu trúc và ngữ nghĩa của các triển khai khác nhau sẽ rất giống nhau đối với tất cả chúng, bao gồm mức độ trừu tượng, cấu trúc dữ liệu được chọn, chúng ' tất cả sẽ giả sử thu gom rác, vv

Ngược lại, giả sử triển khai C so với triển khai Smalltalk của cùng một chức năng sẽ có cấu trúc khác nhau (chức năng và cấu trúc dữ liệu cấp thấp so với đối tượng), tập trung vào các mức độ chi tiết khác nhau (ví dụ: quản lý bộ nhớ thủ công so với thu gom rác ), và hoạt động ở các mức độ trừu tượng khác nhau.

Thế giới quan của không gian thiết kế ngôn ngữ chức năng đơn giản là cụ thể và mạch lạc hơn so với không gian "lập trình mệnh lệnh", kết hợp các cụm, C, Smalltalk, Forth và hàng tá ngôn ngữ hoàn toàn khác nhau thành một thể loại khác nhau.


4

Tôi nghĩ rằng trích dẫn của Simon PJ thực sự là một nhận xét trái chiều.

Sự tương đồng giữa các ngôn ngữ được xác định bởi những gì đã tạo ra sự đồng thuận trong cộng đồng nhà nghiên cứu và thiết kế ngôn ngữ. Không có câu hỏi rằng có một mức độ đồng thuận cao hơn trong cộng đồng lập trình chức năng so với cộng đồng lập trình mệnh lệnh. Nhưng đó cũng là trường hợp các ngôn ngữ lập trình chức năng hầu hết được thiết kế bởi các nhà nghiên cứu hơn là các học viên. Vì vậy, đó là điều tự nhiên cho sự đồng thuận như vậy xuất hiện.

Hầu hết tất cả các ngôn ngữ chức năng đều sử dụng quản lý bộ nhớ được thu thập và cấu trúc dữ liệu đệ quy (có nguồn gốc từ Lisp), hầu hết chúng sử dụng kiểu dữ liệu "đại số" và khớp mẫu (có nguồn gốc từ Hope), rất nhiều trong số chúng sử dụng các hàm bậc cao và hàm đa hình ( có nguồn gốc từ ML). Ngoài ra, sự đồng thuận biến mất. Chúng khác nhau trong các hệ thống mô-đun được sử dụng, cách xử lý các hoạt động thay đổi trạng thái và các hiệu ứng tính toán khác và thứ tự đánh giá (gọi theo tên so với gọi theo giá trị), v.v.

Các ngôn ngữ lập trình mệnh lệnh thường sử dụng các cấu trúc điều khiển lồng nhau (có nguồn gốc từ Algol 60) và các hệ thống loại (có nguồn gốc từ Algol 60, nhưng được củng cố bởi Algol 68). Chúng thường có cú pháp bề mặt cồng kềnh (một lần nữa quay trở lại Algol 60), thực hiện các nỗ lực nửa vời để xử lý các hàm bậc cao và các loại đa hình, và khác nhau trong việc hỗ trợ cấu trúc khối và hệ thống mô-đun. Có lẽ có sự thống nhất hơn trong thứ tự đánh giá bởi vì, sau thập niên 60, tên gọi về cơ bản đã biến mất khỏi các ngôn ngữ mệnh lệnh.

Vì vậy, tôi không rõ ràng rằng sự khác biệt giữa hai loại ngôn ngữ trong tính đồng nhất của chúng là rất lớn.

Sẽ thực sự đáng giá khi đưa các ký hiệu rõ ràng và thống nhất về lập trình chức năng vào các ngôn ngữ lập trình bắt buộc. Tôi thấy rằng Scala đã bắt đầu theo hướng đó. Vẫn còn phải xem liệu xu hướng sẽ tiếp tục.


Tôi ngớ ngẩn tự hỏi tại sao bạn sử dụng các trích dẫn đáng sợ trong các loại dữ liệu "đại số"?
Steven Shaw
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.