Tại sao một số ngôn ngữ lập trình chức năng sử dụng một không gian cho ứng dụng chức năng?


34

Đã xem xét một số ngôn ngữ để lập trình chức năng, tôi luôn tự hỏi tại sao một số ngôn ngữ fp sử dụng một hoặc nhiều ký tự khoảng trắng cho ứng dụng hàm (và định nghĩa), trong khi hầu hết (tất cả?) Các ngôn ngữ bắt buộc / hướng đối tượng đều sử dụng dấu ngoặc đơn, dường như là cách toán học hơn. Tôi cũng nghĩ rằng phong cách sau rõ ràng và dễ đọc hơn nhiều so với không có parens.

Vì vậy, nếu chúng ta có hàm f (x) = x², có hai cách thay thế để gọi nó:

  • Chi cục Kiểm lâm f x

    Ví dụ:

    • ML, Ocaml, F #
    • Haskell
    • LISP, Đề án (bằng cách nào đó)
  • Không phải là FP: f(x)

    Ví dụ:

    • Hầu như tất cả các ngôn ngữ bắt buộc (tôi biết, xem các bình luận / câu trả lời)
    • Erlang
    • Scala (cũng cho phép "ký hiệu toán tử" cho các đối số đơn)

Các lý do cho việc "bỏ đi" dấu ngoặc đơn là gì?


9
Để làm cho nó khó hiểu hơn, erm, tôi có nghĩa là cô đọng .
Den

11
@Den nếu bạn học haskell, cú pháp sẽ là một trong những điều ít gây nhầm lẫn nhất: p
Simon Bergot

5
Bạn có nhận ra sự trớ trêu khi nói rằng sử dụng dấu ngoặc đơn sẽ là toán học hơn và sau đó sử dụng hàm lũy thừa làm ví dụ?
Jörg W Mittag

12
@ Bạn đang làm hại chính mình bằng cách từ chối một ngôn ngữ vì cú pháp của nó. Chắc chắn bạn không gặp khó khăn gì khi học xml hoặc sql (chúng không phải là ngôn ngữ có mục đích chung, nhưng chúng xác định cú pháp của riêng chúng).
Simon Bergot

7
Tôi muốn chỉ ra rằng các kịch bản shell (ví dụ bash) thường không sử dụng tham số để gọi lệnh. Loại PowerShell có điểm kém nhất của cả hai thế giới trong đó các hàm được khai báo bằng dấu ngoặc đơn và được gọi mà không có chúng.
Kris Harper

Câu trả lời:


59

có vẻ là cách toán học hơn

ngôn ngữ chức năng được lấy cảm hứng từ tính toán lambda . Trong trường này, dấu ngoặc đơn không được sử dụng cho ứng dụng hàm.

Tôi cũng nghĩ rằng phong cách sau rõ ràng và dễ đọc hơn nhiều so với không có parens.

Khả năng đọc là trong mắt của kẻ si tình. Bạn không quen đọc nó. Nó hơi giống như các toán tử toán học. Nếu bạn hiểu tính kết hợp, bạn chỉ cần một vài parens để làm rõ cấu trúc biểu thức của bạn. Thường thì bạn không cần chúng.

Currying cũng là một lý do tốt để sử dụng quy ước này. Trong haskell, bạn có thể định nghĩa như sau:

add :: Int -> Int -> Int
add x y = x + y

x = add 5 6 -- x == 11
f = add 5
y = f 6 -- y == 11
z = ((add 5) 6) -- explicit parentheses; z == 11

Với parens, bạn có thể sử dụng hai quy ước: f(5, 6)(không được chế biến) hoặc f(5)(6)(được cung cấp). Cú pháp haskell giúp làm quen với khái niệm currying. Bạn vẫn có thể sử dụng phiên bản không bị quấy rối, nhưng sẽ khó khăn hơn khi sử dụng nó với các tổ hợp

add' :: (Int, Int) -> Int
add' (x, y) = x + y
u = add'(5, 6) -- just like other languages
l = [1, 2, 3]
l1 = map (add 5) l -- [6, 7, 8]
l2 = map (\x -> add'(5, x)) l -- like other languages

Lưu ý cách phiên bản thứ hai buộc bạn phải đăng ký x dưới dạng một biến và biểu thức con là một hàm lấy một số nguyên và thêm 5 vào đó? Phiên bản curried nhẹ hơn nhiều, nhưng cũng được nhiều người coi là dễ đọc hơn.

Các chương trình Haskell sử dụng rộng rãi ứng dụng một phần và các tổ hợp như một phương tiện để xác định và soạn thảo trừu tượng, vì vậy đây không phải là một ví dụ về đồ chơi. Một giao diện chức năng tốt sẽ là một trong đó thứ tự của các tham số cung cấp cách sử dụng thân thiện.

Một điểm khác: một hàm không có tham số nên được gọi với f(). Trong haskell, vì bạn chỉ thao tác các giá trị được đánh giá lười biếng bất biến, bạn chỉ cần viết fvà coi đó là một giá trị sẽ cần thực hiện một số tính toán khi cần. Vì đánh giá của nó sẽ không có bất kỳ tác dụng phụ nào, nên sẽ không có ý nghĩa gì khác về hàm không tham số và giá trị trả về của nó.

Ngoài ra còn có các quy ước khác cho ứng dụng chức năng:

  • Lisp: (fx) - tiền tố có dấu ngoặc đơn bên ngoài
  • Forth: xf - hậu tố

7
Tiểu nitlog: các thuật ngữ là "currying" và "curried", không phải "currifrif" và "currified".
Doval

"không curried" những gì một chức năng không được quấy rối trong haskell?
Ven

3
@ user1737909 Một hàm lấy một tuple làm đối số.
Doval

1
@Doval Thật ra nó phải là kiểu schonfinkeling và đường schonfinkeled. Nhưng tốt, lịch sử luôn xác định người chiến thắng hồi tưởng.
Profpatsch

Suy nghĩ về một cú pháp currying ngoặc đơn, một cái gì đó giống f(a, ,b)hoặc add(a, )hoặc add(, b)có vẻ linh hoạt hơn theo mặc định.
phresnel

25

Ý tưởng cơ bản là làm cho thao tác quan trọng nhất (ứng dụng hàm) dễ đọc nhất và dễ viết nhất. Một không gian rất khó đọc và rất dễ gõ.

Lưu ý rằng điều này không dành riêng cho các ngôn ngữ chức năng, ví dụ như trong Smalltalk , một trong những ngôn ngữ và ngôn ngữ OO đầu tiên được lấy cảm hứng từ nó ( Self , Drameak , Objective-C), mà còn trong Io và các ngôn ngữ được truyền cảm hứng từ nó (Ioke, Seph), khoảng trắng được sử dụng cho các cuộc gọi phương thức.

Kiểu nhỏ:

anArray sort
anArray add: 2
aString replace: "a" with: "b"

(Trong trường hợp sau, tên của phương thức là replace:with:.)

Phong cách Io:

anArray sort
anArray add(2)
aString replace("a", "b")

Scala cũng cho phép khoảng trắng cho các cuộc gọi phương thức:

foo bar(baz)

Và bỏ dấu ngoặc đơn nếu chỉ có một đối số:

foo bar baz

Ruby cũng cho phép bạn bỏ dấu ngoặc đơn:

an_array.sort
an_array.add 2
a_string.replace "a", "b"

sử dụng dấu ngoặc đơn, dường như là cách toán học hơn

Không thực sự:

f(x)
sin x
x²
|x|
x!
x + y
xy
½

Ký hiệu toán học đã phát triển trong một thời gian dài theo cách không nhất quán khủng khiếp.

Nếu ở tất cả, các ngôn ngữ chức năng lấy cảm hứng từ-tính toán trong đó ứng dụng hàm được viết bằng khoảng trắng.


3
Ngoài ra còn có một đối số cho nền kinh tế chỉnh sửa. Đặc biệt, các ngôn ngữ chức năng thường hỗ trợ thành phần trực tiếp. Đặt dấu ngoặc quanh hàm thay vì đối số có ý nghĩa. Bạn chỉ phải thực hiện "một lần": (e. F. G) x trái ngược với e (f (g (x))). Ngoài ra, Haskell, ít nhất, sẽ không ngăn người ta làm f (x) thay vì f x. Nó chỉ không thành ngữ.
Tên của

18

Dấu ngoặc đơn cho ứng dụng chức năng chỉ là một trong số rất nhiều yên ngựa mà Euler để lại cho chúng tôi. Giống như bất cứ điều gì khác toán học cần các quy ước khi có một số cách để làm một cái gì đó. Nếu giáo dục toán học của bạn chỉ mở rộng đến mức không phải là môn học ở trường đại học thì có lẽ bạn không quá quen thuộc với nhiều lĩnh vực mà ứng dụng hàm xảy ra một cách vui vẻ mà không có bất kỳ dấu ngoặc nào (ví dụ: Hình học vi phân, Đại số trừu tượng). Và bạn thậm chí không đề cập đến các hàm được áp dụng infix (hầu hết tất cả các ngôn ngữ), "tiền tố" như lấy một quy tắc hoặc theo sơ đồ (Befunge, Topology đại số).

Vì vậy, trong câu trả lời tôi nói rằng đó là do tỷ lệ cao hơn nhiều của các lập trình viên chức năng và nhà thiết kế ngôn ngữ có giáo dục toán học sâu rộng. Von Neumann nói "Chàng trai trẻ, trong toán học, bạn không hiểu gì cả. Bạn chỉ cần quen với chúng.", Chắc chắn điều này đúng với ký hiệu.


7
Thậm chí không cho tôi bắt đầu vào f(x)vs sin xvs vs |x|vs x!vs x + yvs xyvs ½vs một tổng kết so với một vs. không thể thiếu ...
Jörg W Mittag

2
+1 cho trích dẫn von Neuman. +1 cho phần còn lại của câu trả lời, nhưng tôi không thể cho +2. :(
pvorb

2
Tôi bắt được một gợi ý của tinh hoa ở đây; Tôi tranh luận về tính trung lập của "các nhà thiết kế ngôn ngữ lập trình chức năng được giáo dục tốt hơn" .
CaptainCodeman

7
@CaptainCodeman Ông nói rằng được giáo dục nhiều hơn về toán học, một tuyên bố mà tôi đồng ý. Ít nhất là khi nói đến Haskell, bạn có thể nhận thấy rằng cả hai khái niệm này có tính toán học cao hơn và cộng đồng cũng nghiêng về mặt toán học hơn. Họ không thông minh hơn, chỉ được giáo dục tốt hơn về toán học.
Paul

5
@CaptainCodeman Không, vì anh ta không bao giờ ngụ ý rằng họ được giáo dục nhiều hơn nói chung, chỉ được giáo dục nhiều hơn về toán học. Đó chính xác là những gì ông nói: "giáo dục toán học mở rộng". :)
Paul

8

Mặc dù có rất nhiều sự thật trong câu trả lời của Simon, tôi nghĩ đó cũng là một lý do thực tế hơn nhiều. Bản chất của lập trình chức năng có xu hướng tạo ra nhiều dấu ngoặc đơn hơn so với lập trình bắt buộc, do chuỗi chức năng và thành phần. Những kiểu kết nối và bố cục cũng thường có thể được biểu diễn rõ ràng mà không có dấu ngoặc đơn.

Điểm mấu chốt là, tất cả những dấu ngoặc đơn đó đều gây khó chịu khi đọc và theo dõi. Đây có lẽ là lý do số một LISP không phổ biến hơn. Vì vậy, nếu bạn có thể có được sức mạnh của lập trình chức năng mà không gặp phải sự khó chịu của dấu ngoặc đơn thừa, tôi nghĩ các nhà thiết kế ngôn ngữ sẽ có xu hướng đi theo hướng đó. Rốt cuộc, các nhà thiết kế ngôn ngữ cũng là người sử dụng ngôn ngữ.

Ngay cả Scala cũng cho phép lập trình viên bỏ qua dấu ngoặc đơn trong một số trường hợp nhất định, điều này xảy ra khá thường xuyên khi lập trình theo kiểu chức năng, vì vậy bạn sắp xếp tốt nhất cả hai thế giới. Ví dụ:

val message = line split "," map (_.toByte)

Các parens ở cuối là cần thiết cho sự kết hợp, và những cái khác bị bỏ đi (cũng như các dấu chấm). Viết nó theo cách này nhấn mạnh tính chất xích của các hoạt động bạn đang thực hiện. Nó có thể không quen thuộc với một lập trình viên cấp bách, nhưng với một lập trình viên chức năng, nó cảm thấy rất tự nhiên và trôi chảy khi viết theo cách này, mà không phải dừng lại và chèn cú pháp không có ý nghĩa gì với chương trình, nhưng chỉ ở đó để làm cho trình biên dịch hài lòng .


2
"Điểm mấu chốt là, tất cả các dấu ngoặc đơn đó đều gây khó chịu khi đọc và theo dõi. Có lẽ đó là lý do số một LISP không phổ biến hơn.": Tôi không nghĩ rằng dấu ngoặc đơn là lý do khiến Lisp không phổ biến: Tôi không nghĩ chúng đặc biệt khó đọc và tôi thấy các ngôn ngữ khác có cú pháp khó xử hơn nhiều.
Giorgio

2
Cú pháp LISP phần lớn bù cho các dấu ngoặc đơn gây khó chịu với mức độ trực giao siêu cao của nó. Điều đó chỉ có nghĩa là nó có các tính năng đổi thưởng khác khiến nó vẫn có thể sử dụng được, không phải là các parens không gây phiền nhiễu. Tôi hiểu không phải ai cũng cảm thấy như vậy, nhưng đại đa số các lập trình viên mà tôi đã gặp đều làm như vậy.
Karl Bielefeldt

"Mất trong ngoặc đơn ngu ngốc" là từ viết tắt LISP yêu thích của tôi. Máy tính cũng có thể sử dụng cách nhân đạo để chỉ định các khối mã và loại bỏ sự dư thừa, như trong các ngôn ngữ như Wisp .
Cees Timmerman

4

Cả hai cơ sở đều sai.

  • Các ngôn ngữ chức năng này không sử dụng không gian cho ứng dụng chức năng. Những gì họ làm chỉ đơn giản là phân tích bất kỳ biểu thức nào xuất hiện sau một hàm làm đối số.

    GHCi> f 3
    4
    GHCi> f (3)
    4
    GHCi> (f) 3
    4

    Tất nhiên, nếu bạn không sử dụng một khoảng trắng hay parens thì nó thường sẽ không hoạt động, đơn giản là vì biểu thức không được token hóa đúng cách

    GHCi> f3

    <‌interactive>: 7: 1:
        Không thuộc phạm vi: 'f3'
        Có lẽ bạn có nghĩa là 'f' (dòng 2)

    Nhưng nếu các ngôn ngữ này bị giới hạn ở tên biến 1 ký tự thì nó cũng hoạt động.

  • Các quy ước toán học cũng không yêu cầu dấu ngoặc đơn cho ứng dụng hàm. Các nhà toán học không chỉ thường viết sin x - cho các hàm tuyến tính (thường được gọi là toán tử tuyến tính), việc viết đối số không có parens cũng rất bình thường, có lẽ vì (giống như bất kỳ hàm nào trong lập trình hàm), các hàm tuyến tính thường được xử lý mà không cung cấp trực tiếp một cuộc tranh cãi.

Vì vậy, thực sự, câu hỏi của bạn tập trung vào: tại sao một số ngôn ngữ yêu cầu dấu ngoặc đơn cho ứng dụng chức năng? Vâng, rõ ràng một số người, như bạn, xem xét điều này dễ đọc hơn. Tôi hoàn toàn không đồng ý với điều đó: một cặp parens là tốt, nhưng ngay khi bạn có thêm hai trong số chúng được lồng vào nhau, nó bắt đầu trở nên khó hiểu. Mã Lisp thể hiện điều này tốt nhất, và khá nhiều ngôn ngữ chức năng sẽ trông lộn xộn tương tự nếu bạn cần parens ở mọi nơi. Điều này không xảy ra quá nhiều trong các ngôn ngữ bắt buộc, nhưng chủ yếu là vì các ngôn ngữ này không đủ sức biểu cảm để viết ngắn gọn, rõ ràng một lớp lót ngay từ đầu.


Có tôi, câu hỏi không hoàn hảo. :) Vì vậy, kết luận rút ra là: "Dấu ngoặc không quy mô."
pvorb

2
Cũng không phải là ngôn ngữ không có chức năng đó nhất trí trong đòi hỏi ngoặc; đối với một vài ví dụ, Smalltalk có object method: args, Objective C có [object method: args]và cả Ruby và Perl đều cho phép bỏ dấu ngoặc trong các lệnh gọi hàm và phương thức, chỉ yêu cầu chúng giải quyết sự mơ hồ.
hobbs

1

Một sự làm rõ lịch sử nhỏ: ký hiệu ban đầu trong toán học không có dấu ngoặc đơn !

Ký hiệu fx cho một hàm tùy ý của x được Johan Bernoulli giới thiệu vào khoảng năm 1700 ngay sau khi Leibniz bắt đầu nói về các hàm (thực ra Bernoulli đã viết ϕ x ). Nhưng trước đó, nhiều người đã sử dụng các ký hiệu như sin x hoặc lx (logarit của x ) cho các chức năng cụ thể của x , không có dấu ngoặc đơn. Tôi nghi ngờ đó là lý do nhiều người ngày nay vẫn viết sin x thay vì sin (x) .

Euler, từng là học sinh của Bernoulli, đã nhận nuôi Bernoulli ϕ x và đổi chữ Hy Lạp thành một chữ cái Latinh, viết fx . Sau đó, anh nhận thấy rằng có thể dễ nhầm lẫn giữa f về "số lượng" và tin rằng fx là một sản phẩm, vì vậy anh bắt đầu viết f: x . Do đó ký hiệu f (x) không phải do Euler. Tất nhiên Euler cần dấu ngoặc đơn cho cùng một lý do chúng ta vẫn cần dấu ngoặc đơn trong các ngôn ngữ lập trình chức năng: để phân biệt ví dụ (fx) + y với f (x + y) . Tôi nghi ngờ rằng mọi người sau khi Euler chấp nhận dấu ngoặc đơn là mặc định vì họ chủ yếu thấy Euler viết các biểu thức ghép như thế nào f (ax + b). Anh hiếm khi viếtfx .

Để biết thêm về điều này, hãy xem: Euler có bao giờ viết f (x) với dấu ngoặc đơn không? .

Tôi không biết liệu các nhà thiết kế ngôn ngữ lập trình chức năng hay người phát minh ra phép tính lambda có nhận thức được nguồn gốc lịch sử của ký hiệu của họ hay không. Cá nhân tôi thấy ký hiệu không có dấu ngoặc đơn tự nhiên hơn.

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.