Thiết kế cú pháp - Tại sao nên sử dụng dấu ngoặc đơn khi không có đối số nào được thông qua?


66

Trong nhiều ngôn ngữ, cú pháp function_name(arg1, arg2, ...)được sử dụng để gọi một hàm. Khi chúng ta muốn gọi hàm mà không có bất kỳ đối số nào, chúng ta phải làm function_name().

Tôi thấy thật kỳ lạ khi trình biên dịch hoặc trình thông dịch kịch bản sẽ yêu cầu ()phát hiện thành công nó như một lệnh gọi hàm. Nếu một biến được biết là có thể gọi được, tại sao sẽ không function_name;đủ?

Mặt khác, trong một số ngôn ngữ chúng ta có thể làm: function_name 'test';hoặc thậm chí function_name 'first' 'second';để gọi một chức năng hoặc một lệnh.

Tôi nghĩ rằng dấu ngoặc đơn sẽ tốt hơn nếu chúng chỉ cần khai báo thứ tự ưu tiên và ở những nơi khác là tùy chọn. Ví dụ, làm if expression == true function_name;nên có giá trị như if (expression == true) function_name();.

Điều khó chịu nhất theo ý kiến ​​của tôi là phải làm gì 'SOME_STRING'.toLowerCase()khi rõ ràng không cần đối số cho hàm nguyên mẫu. Tại sao các nhà thiết kế quyết định chống lại đơn giản hơn 'SOME_STRING'.lower?

Tuyên bố miễn trừ trách nhiệm: Đừng hiểu sai ý tôi, tôi thích cú pháp giống như C! ;) Tôi chỉ hỏi liệu nó có thể tốt hơn không. Có yêu cầu ()có bất kỳ lợi thế hiệu suất, hoặc nó làm cho việc hiểu mã dễ dàng hơn? Tôi thực sự tò mò về lý do chính xác là gì.


103
Bạn sẽ làm gì nếu bạn muốn truyền một hàm làm đối số cho hàm khác?
Vincent Savard

30
Miễn là mã cần thiết để được đọc bởi con người, khả năng đọc là vua.
Tulains Córdova

75
Đó là điều có thể đọc được, bạn hỏi về (), nhưng điều nổi bật trong bài viết của bạn là if (expression == true)tuyên bố. Bạn lo lắng về những thứ không cần thiết (), nhưng sau đó sử dụng một thứ không cần thiết == true:)
David Arno

15
Visual Basic cho phép điều này
edc65

14
Nó là bắt buộc trong Pascal, bạn chỉ sử dụng parens cho các hàm và thủ tục lấy tham số.
RemcoGerlich

Câu trả lời:


251

Đối với các ngôn ngữ sử dụng các hàm hạng nhất , điều khá phổ biến là cú pháp tham chiếu đến một hàm là:

a = object.functionName

trong khi hành động gọi hàm đó là:

b = object.functionName()

atrong ví dụ trên sẽ được tham chiếu đến hàm trên (và bạn có thể gọi nó bằng cách thực hiện a()), trong khi bsẽ chứa giá trị trả về của hàm.

Mặc dù một số ngôn ngữ có thể thực hiện các cuộc gọi hàm mà không có dấu ngoặc đơn, nhưng nó có thể gây nhầm lẫn cho dù chúng đang gọi hàm hay chỉ đơn giản là tham chiếu đến hàm.


9
Bạn có thể muốn đề cập rằng sự khác biệt này chỉ thú vị khi có tác dụng phụ - đối với một chức năng thuần túy không thành vấn đề
Bergi

73
@Bergi: Nó quan trọng rất nhiều cho các chức năng thuần túy! Nếu tôi có một hàm make_listđóng gói các đối số của nó vào một danh sách, sẽ có một sự khác biệt lớn giữa f(make_list)việc chuyển một hàm đến fhoặc chuyển một danh sách trống.
dùng2357112

12
@Bergi: Ngay cả khi đó, tôi vẫn muốn có thể chuyển SATInstance.solvehoặc một số chức năng thuần cực kỳ đắt tiền khác sang chức năng khác mà không cần chạy ngay lập tức. Nó cũng làm cho mọi thứ thực sự khó xử nếu someFunctionlàm một cái gì đó khác nhau tùy thuộc vào việc someFunctionđược tuyên bố là tinh khiết. Ví dụ, nếu tôi có (giả) func f() {return g}, func g() {doSomethingImpure}func apply(x) {x()}, sau đó apply(f)gọi f. Nếu sau đó tôi tuyên bố đó flà thuần túy, thì đột nhiên apply(f)chuyển gđến apply, và applygọi gvà làm một cái gì đó không trong sạch.
user2357112

6
@ user2357112 Chiến lược đánh giá độc lập với độ tinh khiết. Sử dụng cú pháp của các cuộc gọi như một chú thích khi nào để đánh giá những gì có thể (nhưng có lẽ khó xử), tuy nhiên nó không thay đổi kết quả hoặc tính chính xác của nó. Về ví dụ của bạn apply(f), nếu gkhông tinh khiết (và applycũng vậy?) Thì toàn bộ sự việc là không trong sạch và tất nhiên bị phá vỡ.
Bergi

14
Ví dụ về điều này là Python và ECMAScript, cả hai đều không thực sự có khái niệm cốt lõi về "phương thức", thay vào đó bạn có một thuộc tính được đặt tên trả về một đối tượng hàm hạng nhất ẩn danh và sau đó bạn gọi hàm hạng nhất ẩn danh đó. Cụ thể, trong cả Python và ECMAScript, nói rằng foo.bar()không phải là một hoạt động "phương thức gọi barđối tượng foo" mà là hai hoạt động, "trường biểu thị barcủa đối tượng foo và sau đó gọi đối tượng được trả về từ trường đó". Vì vậy, .toán tử chọn trường()toán tử cuộc gọi và chúng là độc lập.
Jörg W Găngag

63

Thật vậy, Scala cho phép điều này, mặc dù có một quy ước được tuân theo: nếu phương thức này có tác dụng phụ, dù sao thì nên sử dụng dấu ngoặc đơn.

Là một người viết trình biên dịch, tôi sẽ thấy sự hiện diện được bảo đảm của dấu ngoặc đơn khá thuận tiện; Tôi sẽ luôn biết rằng đó là một cuộc gọi phương thức và tôi sẽ không phải xây dựng một phân nhánh cho trường hợp kỳ lạ.

Là một lập trình viên và người đọc mã, sự hiện diện của dấu ngoặc đơn không nghi ngờ gì rằng đó là một cuộc gọi phương thức, mặc dù không có tham số nào được thông qua.

Việc truyền tham số không phải là đặc tính xác định duy nhất của lệnh gọi phương thức. Tại sao tôi lại xử lý một phương thức không tham số khác với phương thức có tham số?


Cảm ơn, và bạn đã đưa ra lý do hợp lệ là tại sao điều này lại quan trọng. Bạn cũng có thể đưa ra một số ví dụ về lý do tại sao các nhà thiết kế ngôn ngữ nên sử dụng các chức năng trong các nguyên mẫu không?
David Refoua

sự hiện diện của dấu ngoặc đơn không còn nghi ngờ gì nữa, đó là một cuộc gọi phương thức mà tôi hoàn toàn đồng ý. Tôi thích ngôn ngữ lập trình của tôi rõ ràng hơn là ẩn.
Corey Ogburn

20
Scala thực sự tinh tế hơn một chút so với điều đó: trong khi các ngôn ngữ khác chỉ cho phép một danh sách tham số có 0 hoặc nhiều tham số, Scala cho phép không hoặc nhiều danh sách tham số có 0 hoặc nhiều tham số. Vì vậy, def foo()là một phương thức có một danh sách tham số trống và def foolà một phương thức không có danh sách tham số và hai phương thức là những thứ khác nhau ! Nói chung, một phương thức không có danh sách tham số cần được gọi mà không có danh sách đối số và phương thức có danh sách tham số trống cần được gọi với danh sách đối số trống. Gọi một phương thức không có danh sách tham số với một đối số trống thực sự là
LỚN

19
Sọ được hiểu là gọi applyphương thức của đối tượng được trả về bởi lệnh gọi phương thức với danh sách đối số trống, trong khi gọi phương thức có danh sách tham số trống không có danh sách đối số có thể được hiểu theo cách khác nhau khi gọi phương thức với danh sách đối số trống, expansion-mở rộng thành một phương thức được áp dụng một phần (nghĩa là "tham chiếu phương thức") hoặc nó có thể không biên dịch được. Ngoài ra, Scala tuân theo Nguyên tắc truy cập thống nhất, bằng cách không phân biệt (về mặt cú pháp) giữa việc gọi một phương thức mà không có danh sách đối số và tham chiếu một trường.
Jörg W Mittag

3
Ngay cả khi tôi đánh giá cao Ruby vì không có "nghi lễ" bắt buộc - parens, niềng răng, loại rõ ràng - tôi có thể nói rằng ngoặc đơn phương thức đọc nhanh hơn ; nhưng một khi thành ngữ quen thuộc thì Ruby khá dễ đọc và chắc chắn sẽ nhanh hơn để viết.
radarbob

19

Đây thực sự là một sự thay đổi khá tinh tế của các lựa chọn cú pháp. Tôi sẽ nói với các ngôn ngữ chức năng, dựa trên phép tính lambda đã gõ.

Trong các ngôn ngữ đã nói, mọi hàm đều có chính xác một đối số . Những gì chúng ta thường nghĩ là "nhiều đối số" thực sự là một tham số duy nhất của loại sản phẩm. Vì vậy, ví dụ, hàm so sánh hai số nguyên:

leq : int * int -> bool
leq (a, b) = a <= b

mất một cặp số nguyên. Các dấu ngoặc không biểu thị các tham số hàm; chúng được sử dụng để khớp mẫu với đối số. Để thuyết phục bạn rằng đây thực sự là một đối số, thay vào đó chúng ta có thể sử dụng các hàm chiếu thay vì khớp mẫu để giải cấu trúc cặp:

leq some_pair = some_pair.1 <= some_pair.2

Do đó, dấu ngoặc đơn thực sự là một tiện lợi cho phép chúng ta tạo mẫu khớp và lưu một số cách gõ. Họ không bắt buộc.

Điều gì về một chức năng mà bề ngoài không có đối số? Một chức năng như vậy thực sự có tên miền Unit. Thành viên duy nhất Unitthường được viết là (), vì vậy đó là lý do tại sao dấu ngoặc đơn xuất hiện.

say_hi : Unit -> string
say_hi a = "Hi buddy!"

Để gọi hàm này, chúng ta sẽ phải áp dụng nó cho một giá trị kiểu Unit, phải ()như vậy, vì vậy chúng ta kết thúc bằng văn bản say_hi ().

Vì vậy, thực sự không có thứ gọi là danh sách đối số!


3
Và đó là lý do tại sao dấu ngoặc rỗng ()giống như thìa trừ tay cầm.
Mindwin

3
Xác định một leqchức năng sử dụng <chứ không phải <=là khá khó hiểu!
KRyan

5
Câu trả lời này có thể dễ hiểu hơn nếu nó sử dụng từ "tuple" ngoài các cụm từ như "loại sản phẩm".
Kevin

Hầu hết các hình thức sẽ coi int f (int x, int y) là một hàm từ I ^ 2 đến I. Tính toán Lambda là khác nhau, nó không sử dụng phương pháp này để khớp với những gì trong các hình thức khác là tính cao. Thay vào đó tính toán lambda sử dụng cà ri. So sánh sẽ là x -> (y -> (x <= y)). (x -> (y -> (x <= y)) 2 sẽ đánh giá thành (y-> 2 <= y) và (x -> (y -> (x <= y)) 2 3 sẽ đánh giá ( y-> 2 <= y) 3 lần lượt ước tính thành 2 <= 3.
Taemyr

1
@PeriataBreatta Tôi không quen thuộc với lịch sử sử dụng ()để đại diện cho đơn vị. Tôi sẽ không ngạc nhiên nếu ít nhất một phần lý do là giống với "hàm không tham số". Tuy nhiên, có một lời giải thích khả dĩ khác cho cú pháp. Trong đại số của các loại, Unittrên thực tế là bản sắc cho các loại sản phẩm. Một sản phẩm nhị phân được viết dưới dạng (a,b), chúng ta có thể viết một sản phẩm đơn nguyên (a), vì vậy một phần mở rộng hợp lý sẽ là viết sản phẩm vô giá trị một cách đơn giản ().
vườn

14

Ví dụ, trong Javascript sử dụng tên phương thức không có () sẽ trả về hàm mà không thực hiện nó. Bằng cách này, bạn có thể chuyển hàm dưới dạng đối số sang phương thức khác.

Trong Java, sự lựa chọn được đưa ra là một mã định danh theo sau () hoặc (...) có nghĩa là một cuộc gọi phương thức trong khi một mã định danh không có () đề cập đến một biến thành viên. Điều này có thể cải thiện khả năng đọc, vì bạn không nghi ngờ liệu bạn đang xử lý một phương thức hay một biến thành viên. Trong thực tế, cùng một tên có thể được sử dụng cho cả một phương thức và một biến thành viên, cả hai sẽ có thể truy cập được bằng cú pháp tương ứng của chúng.


4
+1 Miễn là mã cần được đọc bởi con người, khả năng đọc là vua.
Tulains Córdova

1
@ TulainsCórdova Tôi không chắc chắn perl đồng ý với bạn.
Racheet

@Racheet: Perl có thể không nhưng Perl Best Practiceskhông
slebetman

1
@Racheet Perl rất dễ đọc nếu nó được viết để có thể đọc được. Điều đó giống nhau đối với hầu hết mọi ngôn ngữ phi bí truyền. Perl ngắn gọn, súc tích rất dễ đọc đối với các lập trình viên Perl, giống như mã Scala hoặc Haskell có thể đọc được cho những người có kinh nghiệm trong các ngôn ngữ đó. Tôi cũng có thể nói tiếng Đức với bạn một cách rất phức tạp, hoàn toàn chính xác về mặt ngữ pháp, nhưng bạn vẫn không hiểu một từ nào, ngay cả khi bạn thành thạo tiếng Đức. Đó cũng không phải lỗi của người Đức. ;)
simbabque

trong java nó không "xác định" một phương thức. Nó gọi nó. Object::toStringxác định một phương thức.
njzk2

10

Cú pháp tuân theo ngữ nghĩa, vì vậy hãy bắt đầu từ ngữ nghĩa:

Các cách sử dụng một chức năng hoặc phương pháp là gì?

Thực tế, có nhiều cách:

  • một hàm có thể được gọi, có hoặc không có đối số
  • một chức năng có thể được coi là một giá trị
  • một hàm có thể được áp dụng một phần (tạo một bao đóng lấy ít nhất một đối số ít hơn và đóng trên các đối số đã truyền)

Có một cú pháp tương tự cho các mục đích sử dụng khác nhau là cách tốt nhất để tạo ra một ngôn ngữ mơ hồ hoặc ít nhất là một ngôn ngữ khó hiểu (và chúng ta có đủ những thứ đó).

Trong các ngôn ngữ C và C:

  • việc gọi một hàm được thực hiện bằng cách sử dụng dấu ngoặc đơn để đặt các đối số (có thể không có) như trong func()
  • việc xử lý một hàm như một giá trị có thể được thực hiện bằng cách sử dụng tên của nó như trong &func(C tạo một con trỏ hàm)
  • trong một số ngôn ngữ, bạn có các cú pháp ngắn để áp dụng một phần; someVariable::someMethodVí dụ, Java cho phép (giới hạn trong trình nhận phương thức, nhưng vẫn hữu ích)

Lưu ý cách mỗi cách sử dụng có một cú pháp khác nhau, cho phép bạn phân biệt chúng dễ dàng.


2
Chà, "dễ dàng" là một trong những tình huống "chỉ rõ ràng nếu nó đã được hiểu". Một người mới bắt đầu sẽ hoàn toàn hợp lý khi lưu ý rằng nó không có nghĩa là rõ ràng mà không giải thích tại sao một số dấu câu có ý nghĩa của nó.
Eric Lippert

2
@EricLippert: Thật vậy, dễ dàng không nhất thiết là trực quan. Mặc dù trong trường hợp này tôi sẽ chỉ ra rằng dấu ngoặc đơn cũng được sử dụng cho chức năng "gọi" trong toán học. Điều đó đang được nói, tôi đã tìm thấy người mới bắt đầu trong nhiều ngôn ngữ đấu tranh nhiều hơn với các khái niệm / ngữ nghĩa hơn là cú pháp nói chung.
Matthieu M.

Câu trả lời này nhầm lẫn giữa sự khác biệt giữa "currying" và "ứng dụng một phần". Các ::nhà điều hành trong Java là một nhà điều hành ứng dụng một phần, không phải là một nhà điều hành currying. Ứng dụng một phần là một hoạt động có một hàm và một hoặc nhiều giá trị và trả về một hàm chấp nhận ít giá trị hơn. Currying chỉ hoạt động trên các chức năng, không phải giá trị.
Periata Breatta

@PeriataBreatta: Điểm tốt, cố định.
Matthieu M.

8

Trong một ngôn ngữ có tác dụng phụ, IMO thực sự hữu ích để phân biệt giữa việc đọc (trong lý thuyết tác dụng phụ miễn phí) của một biến

variable

và gọi một chức năng, có thể phát sinh tác dụng phụ

launch_nukes()

OTOH, nếu không có tác dụng phụ (trừ những tác dụng được mã hóa trong hệ thống loại) thì sẽ không có điểm nào khác biệt giữa việc đọc một biến và gọi một hàm không có đối số.


1
Lưu ý rằng OP đã trình bày trường hợp trong đó một đối tượng là đối số duy nhất và được trình bày trước tên hàm (cũng cung cấp phạm vi cho tên hàm). noun.verbcú pháp có thể rõ ràng về ý nghĩa noun.verb()và ít lộn xộn hơn. Tương tự, một hàm member_trả về một tham chiếu bên trái có thể cung cấp một cú pháp thân thiện hơn một hàm setter điển hình: obj.member_ = new_valuevs. obj.set_member(new_value)(hậu tố _là một gợi ý nói "phơi bày" gợi nhớ các _ tiền tố cho các hàm "ẩn").
Paul A. Clayton

1
@ PaulA.Clayton Tôi không thấy điều này không được bao phủ bởi câu trả lời của tôi: Nếu noun.verbcó nghĩa là gọi chức năng, làm thế nào để bạn phân biệt nó với quyền truy cập của thành viên?
Daniel Jour

1
Liên quan đến việc đọc các biến không có tác dụng phụ về mặt lý thuyết, tôi chỉ muốn nói điều này: Luôn luôn cảnh giác với những người bạn giả dối .
Thời gian Justin

8

Không có câu trả lời nào khác đã cố gắng giải quyết câu hỏi: cần có bao nhiêu sự dư thừa trong thiết kế ngôn ngữ? Bởi vì ngay cả khi bạn có thể thiết kế một ngôn ngữ để x = sqrt yđặt x thành căn bậc hai của y, điều đó không có nghĩa là bạn nhất thiết phải làm.

Trong một ngôn ngữ không có sự dư thừa, mỗi chuỗi ký tự có nghĩa là một cái gì đó, có nghĩa là nếu bạn mắc một lỗi duy nhất, bạn sẽ không nhận được thông báo lỗi, chương trình của bạn sẽ làm sai, đó có thể là một cái gì đó rất khác với những gì bạn dự định và rất khó để gỡ lỗi. (Như bất kỳ ai từng làm việc với các biểu thức thông thường đều sẽ biết.) Một chút dư thừa là một điều tốt vì nó cho phép phát hiện ra nhiều lỗi của bạn và càng có nhiều sự dư thừa thì khả năng chẩn đoán sẽ càng chính xác. Bây giờ tất nhiên, bạn có thể đi quá xa (không ai trong chúng ta muốn viết bằng COBOL những ngày này), nhưng có một sự cân bằng là đúng.

Dự phòng cũng giúp dễ đọc, bởi vì có nhiều manh mối hơn. Tiếng Anh không có dư thừa sẽ rất khó đọc, và điều tương tự cũng đúng với ngôn ngữ lập trình.


Theo một nghĩa nào đó, tôi đồng ý: các ngôn ngữ lập trình nên có một cơ chế mạng lưới an toàn trên mạng. Nhưng tôi không đồng ý rằng Cú pháp dư thừa một chút trong cú pháp là một cách hay để giải quyết vấn đề đó - đó là bản tóm tắt , và điều chủ yếu là gây ra tiếng ồn khiến lỗi khó phát hiện hơn và mở ra khả năng cho lỗi cú pháp làm mất tập trung lỗi thực sự. Các loại chi tiết có thể đọc được rất ít liên quan đến cú pháp, nhiều hơn với các quy ước đặt tên . Và một mạng lưới an toàn được triển khai hiệu quả nhất như một hệ thống loại mạnh , trong đó hầu hết các thay đổi ngẫu nhiên sẽ làm cho chương trình không được gõ.
leftaroundabout

@leftaroundabout: Tôi thích nghĩ về các quyết định thiết kế ngôn ngữ theo khoảng cách Hamming. Nếu hai cấu trúc có ý nghĩa khác nhau, thường có ích khi chúng khác nhau theo ít nhất hai cách và có một hình thức khác nhau chỉ theo một cách tạo ra một squawk trình biên dịch. Nhà thiết kế ngôn ngữ nói chung không thể chịu trách nhiệm đảm bảo rằng các số nhận dạng có thể dễ dàng phân biệt, nhưng nếu ví dụ yêu cầu :=và so sánh ==, =chỉ có thể sử dụng để khởi tạo thời gian biên dịch, thì khả năng lỗi chính tả biến các phép so sánh thành bài tập sẽ bị giảm đi rất nhiều.
supercat

@leftaroundabout: LIkewise, nếu tôi đang thiết kế một ngôn ngữ, tôi có thể sẽ yêu cầu ()gọi hàm zero-argument nhưng cũng yêu cầu mã thông báo để "lấy địa chỉ" của hàm. Hành động trước đây phổ biến hơn nhiều, vì vậy việc lưu mã thông báo trong trường hợp ít phổ biến hơn không phải là hữu ích.
supercat

@supercat: tốt, một lần nữa tôi nghĩ rằng điều này đang giải quyết vấn đề ở cấp độ sai. Chuyển nhượng và so sánh là các hoạt động khác nhau về mặt khái niệm, do đó, đánh giá chức năng và lấy địa chỉ chức năng, tương ứng. Do đó, chúng nên có các loại khác biệt rõ ràng, sau đó nó không thực sự quan trọng nữa nếu các toán tử có khoảng cách Hamming thấp, bởi vì bất kỳ lỗi đánh máy nào cũng sẽ là lỗi loại thời gian biên dịch.
leftaroundabout

@leftaroundabout: Nếu một phép gán đang được thực hiện cho biến kiểu boolean hoặc nếu kiểu trả về của hàm tự nó là một con trỏ tới một hàm (hoặc - trong một số ngôn ngữ - một hàm thực tế), thay thế một phép so sánh bằng một phép gán, hoặc một lệnh gọi hàm với tham chiếu hàm, có thể mang lại một chương trình có giá trị cú pháp, nhưng có ý nghĩa hoàn toàn khác với phiên bản gốc. Kiểm tra kiểu sẽ tìm thấy một số lỗi như vậy nói chung, nhưng làm cho cú pháp khác biệt có vẻ như là một cách tiếp cận tốt hơn.
supercat

5

Trong hầu hết các trường hợp, đây là những lựa chọn cú pháp về ngữ pháp của ngôn ngữ. Nó rất hữu ích cho ngữ pháp cho các cấu trúc riêng lẻ khác nhau (tương đối) không rõ ràng khi được kết hợp tất cả lại với nhau. (Nếu có sự mơ hồ, như trong một số khai báo C ++, phải có các quy tắc cụ thể để phân giải.) Trình biên dịch không có vĩ độ để đoán; nó là cần thiết để làm theo đặc điểm kỹ thuật ngôn ngữ.


Visual Basic, dưới nhiều hình thức khác nhau, phân biệt giữa các thủ tục không trả về giá trị và hàm.

Các thủ tục phải được gọi là một tuyên bố và không yêu cầu parens, chỉ cần các đối số được phân tách bằng dấu phẩy, nếu có. Các hàm phải được gọi là một phần của biểu thức và yêu cầu parens.

Đó là một sự khác biệt tương đối không cần thiết làm cho việc tái cấu trúc thủ công giữa hai hình thức trở nên đau đớn hơn nó phải có.

(Mặt khác Visual Basic sử dụng dấu ngoặc cùng ()để tham khảo mảng như cho các cuộc gọi chức năng, vì vậy tài liệu tham khảo mảng trông giống như chức năng cuộc gọi. Và điều này giúp giảm bớt refactoring tay một mảng thành một cuộc gọi chức năng! Vì vậy, chúng ta có thể suy nghĩ về các ngôn ngữ khác sử dụng []'s để tham khảo mảng, nhưng tôi lạc đề ...)


Trong ngôn ngữ C và C ++, nội dung của các biến được tự động truy cập bằng cách sử dụng tên của chúng và nếu bạn muốn tham chiếu đến chính biến đó thay vì nội dung của nó, bạn áp dụng toán tử đơn nguyên &.

Loại cơ chế này cũng có thể được áp dụng cho tên hàm. Tên hàm thô có thể ám chỉ một lệnh gọi hàm, trong khi đó một toán tử đơn nguyên &sẽ được sử dụng để gọi hàm (chính nó) là dữ liệu. Cá nhân, tôi thích ý tưởng truy cập các hàm không có đối số không có tác dụng phụ với cú pháp giống như các biến.

Điều này là hoàn toàn hợp lý (cũng như các lựa chọn cú pháp khác cho việc này).


2
Tất nhiên, mặc dù thiếu dấu ngoặc trong các cuộc gọi thủ tục là một sự khác biệt không cần thiết so với các lệnh gọi hàm của nó, việc thêm dấu ngoặc bắt buộc vào chúng sẽ là sự phân biệt không cần thiết giữa các câu lệnh , nhiều trong số đó trong ngôn ngữ có nguồn gốc BASIC có hiệu ứng rất gợi nhớ các thủ tục. Cũng đã được một thời gian kể từ khi tôi thực hiện bất kỳ công việc nào trong phiên bản MS BASIC, nhưng nó vẫn không cho phép cú pháp call <procedure name> (<arguments>)? Khá chắc chắn rằng đó là một cách hợp lệ để sử dụng các quy trình trở lại khi tôi thực hiện lập trình QuickBASIC trong một đời khác ...
Periata Breatta

1
@PeriataBreatta: VB vẫn cho phép cú pháp "gọi" và đôi khi yêu cầu nó trong trường hợp thứ được gọi là một biểu thức [ví dụ: người ta có thể nói "Call If (someCondition, Delegate1, Delegate2) (đối số)", nhưng trực tiếp việc gọi kết quả của toán tử "Nếu" sẽ không hợp lệ dưới dạng một câu lệnh độc lập].
supercat

@PeriataBreatta Tôi thích VB6 không có các cuộc gọi thủ tục khung vì nó làm cho mã giống DSL trông đẹp.
Đánh dấu

@supercat Trong LinqPad, thật đáng ngạc nhiên khi tôi thường xuyên phải sử dụng Callcho một phương thức mà tôi hầu như không bao giờ cần trong mã sản xuất "bình thường".
Đánh dấu

5

Tính nhất quán và dễ đọc.

Nếu tôi biết rằng bạn gọi hàm Xnhư thế này: X(arg1, arg2, ...)thì tôi mong nó hoạt động theo cách tương tự để không có đối số : X().

Bây giờ đồng thời tôi biết rằng bạn có thể định nghĩa biến là một số ký hiệu và sử dụng nó như thế này:

a = 5
b = a
...

Bây giờ tôi sẽ nghĩ gì khi tôi tìm thấy điều này?

c = X

Dự đoán của bạn là tốt như của tôi. Trong trường hợp bình thường, Xlà một biến. Nhưng nếu chúng tôi đi theo con đường của bạn, nó cũng có thể là một chức năng! Tôi có nhớ liệu biểu tượng nào ánh xạ tới nhóm nào (biến / hàm) không?

Chúng ta có thể áp đặt các ràng buộc nhân tạo, ví dụ: "Các hàm bắt đầu bằng chữ in hoa. Các biến bắt đầu bằng chữ in thường", nhưng điều đó không cần thiết và làm cho mọi thứ phức tạp hơn, mặc dù đôi khi có thể giúp một số mục tiêu thiết kế.

Lưu ý phụ 1: Các câu trả lời khác hoàn toàn bỏ qua một điều nữa: ngôn ngữ có thể cho phép bạn sử dụng cùng tên cho cả chức năng và biến và phân biệt theo ngữ cảnh. Xem Common Lisplàm ví dụ. Chức năng xvà biến xcùng tồn tại hoàn toàn tốt.

Lưu ý phụ 2: Câu trả lời được chấp nhận cho chúng ta thấy cú pháp : object.functionname. Thứ nhất, nó không phổ biến đối với các ngôn ngữ có chức năng hạng nhất. Thứ hai, là một lập trình viên, tôi sẽ coi đây là một thông tin bổ sung: functionnamethuộc về một object. Cho dù objectlà một đối tượng, một lớp hay một không gian tên không quan trọng lắm, nhưng nó cho tôi biết rằng nó thuộc về một nơi nào đó . Điều này có nghĩa là bạn phải thêm cú pháp nhân tạo object.cho từng chức năng toàn cầu hoặc tạo một số objectđể giữ tất cả các chức năng toàn cầu.

Và bằng cách nào đó, bạn mất khả năng có các không gian tên riêng cho các hàm và biến.


Vâng Tính nhất quán là một lý do đủ tốt, dừng hoàn toàn (không phải là các điểm khác của bạn không hợp lệ - chúng là).
Matthew đọc

4

Để thêm vào các câu trả lời khác, hãy lấy ví dụ C này:

void *func_factory(void)
{
        return 0;
}

void *(*ff)(void);

void example()
{
        ff = func_factory;
        ff = func_factory();
}

Nếu toán tử gọi là tùy chọn, sẽ không có cách nào để phân biệt giữa gán chức năng và gọi hàm.

Điều này thậm chí còn có vấn đề hơn trong các ngôn ngữ thiếu một hệ thống loại, ví dụ JavaScript, trong đó suy luận kiểu không thể được sử dụng để tìm ra cái gì và không phải là một hàm.


3
JavaScript không thiếu một hệ thống loại. Nó thiếu khai báo kiểu . Nhưng nó hoàn toàn nhận thức được sự khác biệt giữa các loại giá trị khác nhau.
Periata Breatta

1
Periata Breatta: điểm quan trọng là các biến và thuộc tính Java có thể chứa các loại giá trị bất kỳ, vì vậy bạn không thể luôn biết liệu đó có phải là một hàm tại thời điểm đọc mã hay không.
Revierpost

Ví dụ, chức năng này làm gì? function cross(a, b) { return a + b; }
Mark K Cowan

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.