Tại sao có quá ít ngôn ngữ với loại 'toán tử' biến?


47

Tôi có nghĩa là theo cách này:

<?php
    $number1 = 5;   // (Type 'Int')
    $operator1 = +; // (Type non-existent 'Operator')
    $number2 = 5;   // (Type 'Int')
    $operator2 = *; // (Type non-existent 'Operator')
    $number3 = 8;   // (Type 'Int')

    $test = $number1 $operator1 $number2 $operator2 $number3; //5 + 5 * 8.

    var_dump($test);
?>

Nhưng cũng theo cách này:

<?php
    $number1 = 5;
    $number3 = 9;
    $operator1 = <;

    if ($number1 $operator1 $number3) { //5 < 9 (true)
        echo 'true';
    }
?>

Dường như không có ngôn ngữ nào có thứ này - có lý do chính đáng tại sao chúng không có?


28
Nói chung, những gì bạn muốn làm sẽ được bao phủ bởi tất cả các ngôn ngữ hỗ trợ một số hình thức lập trình meta với một số loại lambdas, đóng cửa hoặc chức năng ẩn danh sẽ là cách phổ biến để thực hiện các tính năng đó. Với các ngôn ngữ nơi các phương thức là công dân hạng nhất, bạn sẽ có thể sử dụng chúng ít nhiều giống với các biến. Mặc dù không chính xác là cú pháp đơn giản mà bạn sử dụng ở đây vì trong hầu hết các ngôn ngữ như vậy, phải nói rõ rằng bạn thực sự muốn gọi phương thức được lưu trữ trong biến.
thorsten müller 22/03/2016

7
@MartinMaat Ngôn ngữ chức năng làm điều này rất nhiều.
Thorbjørn Ravn Andersen

7
trong haskell, toán tử là các hàm giống như bất kỳ hàm nào khác. loại (+)Num a => a -> a -> aIIRC. bạn cũng có thể xác định các hàm để chúng có thể được viết bằng ( a + bthay vì (+) a b)
sara

5
@enderland: Chỉnh sửa của bạn đã thay đổi hoàn toàn mục tiêu của câu hỏi. Nó đi từ hỏi liệu có ngôn ngữ nào tồn tại không, đến hỏi tại sao rất ít tồn tại. Tôi nghĩ rằng chỉnh sửa của bạn sẽ dẫn đến rất nhiều độc giả nhầm lẫn.
Bryan Oakley

5
@enderland: trong khi đó là sự thật, việc thay đổi hoàn toàn chủ đề chỉ gây nhầm lẫn. Nếu nó lạc đề, cộng đồng sẽ bỏ phiếu để đóng nó. Không có câu trả lời nào (tại thời điểm tôi viết bài này) có ý nghĩa cho câu hỏi như được viết.
Bryan Oakley

Câu trả lời:


104

Các toán tử chỉ là các hàm dưới các tên vui nhộn, với một số cú pháp đặc biệt xung quanh.

Trong nhiều ngôn ngữ, đa dạng như C ++ và Python, bạn có thể xác định lại các toán tử bằng cách ghi đè các phương thức đặc biệt của lớp. Sau đó, các toán tử tiêu chuẩn (ví dụ +) hoạt động theo logic bạn cung cấp (ví dụ: nối chuỗi hoặc thêm ma trận hoặc bất cứ điều gì).

Vì các hàm xác định toán tử như vậy chỉ là các phương thức, bạn có thể chuyển chúng xung quanh như một hàm:

# python
action = int.__add__
result = action(3, 5)
assert result == 8

Các ngôn ngữ khác cho phép bạn xác định trực tiếp các toán tử mới dưới dạng hàm và sử dụng chúng ở dạng infix.

-- haskell
plus a b = a + b  -- a normal function
3 `plus` 5 == 8 -- True

(+++) a b = a + b  -- a funny name made of non-letters
3 +++ 5 == 8 -- True

let action = (+)
1 `action` 3 == 4 -- True

Thật không may, tôi không chắc chắn nếu PHP hỗ trợ bất cứ điều gì như vậy và nếu hỗ trợ nó sẽ là một điều tốt. Sử dụng một chức năng đơn giản, nó dễ đọc hơn $foo $operator $bar.


2
@tac: Vâng, điều này rất hay và thậm chí có thể được chuyển sang các ngôn ngữ khác :) Về Haskell, điều tôi thiếu nhất là bộ phận này là $toán tử làm mờ dấu ngoặc đơn (đặc biệt là nhiều lồng nhau) - nhưng điều này chỉ có thể hoạt động với không Các hàm -variadic, do đó loại trừ Python và Java. Thành phần chức năng unary OTOH có thể được thực hiện độc đáo .
9000

6
Tôi chưa bao giờ là một fan hâm mộ của quá tải toán tử bởi vì có, một toán tử chỉ là một hàm với cú pháp đặc biệt, nhưng có một hợp đồng ngụ ý thường đi với các toán tử không đi với các hàm. "+" chẳng hạn, có những kỳ vọng nhất định - ưu tiên nhà điều hành, giao hoán, v.v. - và đi ngược lại những kỳ vọng đó là một lộ trình chắc chắn để gây nhầm lẫn cho mọi người và tạo ra lỗi. Một lý do mà mặc dù tôi yêu thích javascript, tôi sẽ thích họ phân biệt giữa + để thêm và nối. Perl đã có nó ngay tại đó.
crazy4jesus 22/03/2016

4
Lưu ý rằng Python có một operatormô-đun chuẩn sẽ cho phép bạn viết action = operator.addvà để nó hoạt động cho bất kỳ loại nào xác định +(không chỉ int).
dan04

3
Trong Haskell +hoạt động trên kiểu chữ Num để bạn có thể triển khai +cho bất kỳ loại dữ liệu mới nào bạn tạo, nhưng cũng giống như cách bạn làm bất cứ điều gì khác, ví dụ như fmap cho functor. Haskell rất phù hợp để cho phép người vận hành quá tải, họ phải làm việc chăm chỉ để không cho phép điều đó!
Martin Capodici

17
Không chắc chắn tại sao mọi người đang cố định về quá tải. Câu hỏi ban đầu không phải là về quá tải. Theo tôi, các nhà khai thác là các giá trị hạng nhất. Để viết $operator1 = +và sau đó sử dụng nó một biểu thức bạn không cần phải sử dụng quá tải toán tử !
Andres F.

16

Có rất nhiều ngôn ngữ cho phép một số loại siêu lập trình . Đặc biệt, tôi ngạc nhiên khi thấy không có câu trả lời nào nói về gia đình ngôn ngữ Lisp .

Từ wikipedia:

Metaprogramming là viết các chương trình máy tính với khả năng coi các chương trình là dữ liệu của chúng.

Sau đó trong văn bản:

Lisp có lẽ là ngôn ngữ tinh túy với các phương tiện siêu lập trình, cả vì ưu tiên lịch sử của nó và vì tính đơn giản và sức mạnh của siêu lập trình của nó.

Ngôn ngữ Lisp

Một phần giới thiệu nhanh chóng để Lisp theo sau.

Một cách để xem mã là một bộ hướng dẫn: làm điều này, sau đó làm điều đó, sau đó làm điều khác ... Đây là một danh sách! Một danh sách những điều cho chương trình để làm. Và tất nhiên bạn có thể có danh sách bên trong danh sách để thể hiện các vòng lặp, v.v.

Nếu chúng ta đại diện cho một danh sách có chứa các yếu tố a, b, c, d như thế này: (abcd), chúng tôi có được cái gì đó trông giống như một cuộc gọi chức năng Lisp, nơi alà chức năng, và b, c, dlà các đối số. Nếu thực tế là "Hello World!" chương trình có thể được viết như vậy:(println "Hello World!")

Tất nhiên b, choặc dcó thể là danh sách đánh giá một cái gì đó là tốt. Sau đây: (println "I can add :" (+ 1 3) )sau đó sẽ in "" Tôi có thể thêm: 4 ".

Vì vậy, một chương trình là một chuỗi các danh sách lồng nhau và phần tử đầu tiên là một hàm. Tin tốt là chúng ta có thể thao túng danh sách! Vì vậy, chúng ta có thể thao tác ngôn ngữ lập trình.

Lợi thế Lisp

Lisps không có nhiều ngôn ngữ lập trình nhiều như một bộ công cụ để tạo ngôn ngữ lập trình. Một ngôn ngữ lập trình lập trình.

Điều này không chỉ dễ dàng hơn nhiều trong việc tạo ra các toán tử mới, mà gần như không thể viết một số toán tử bằng các ngôn ngữ khác vì các đối số được đánh giá khi được truyền vào hàm.

Ví dụ, trong một ngôn ngữ giống như C, hãy nói rằng bạn muốn iftự mình viết một toán tử, đại loại như:

my-if(condition, if-true, if-false)

my-if(false, print("I should not be printed"), print("I should be printed"))

Trong trường hợp này, cả hai đối số sẽ được đánh giá và in, theo thứ tự phụ thuộc vào thứ tự đánh giá của các đối số.

Trong Lisps, viết một toán tử (chúng ta gọi nó là macro) và viết một hàm là về cùng một thứ và được sử dụng theo cùng một cách. Sự khác biệt chính là các tham số cho macro không được đánh giá trước khi được chuyển dưới dạng đối số cho macro. Điều này là cần thiết để có thể viết một số toán tử, như iftrên.

Ngôn ngữ trong thế giới thực

Hiển thị chính xác như thế nào là một chút ngoài phạm vi ở đây, nhưng tôi khuyến khích bạn hãy thử lập trình trong một Lisp để tìm hiểu thêm. Chẳng hạn, bạn có thể xem qua:

  • Lược đồ , một Lisp cũ, khá "tinh khiết" với lõi nhỏ
  • Lisp thông thường, một Lisp lớn hơn với hệ thống đối tượng được tích hợp tốt và nhiều triển khai (nó được chuẩn hóa ANSI)
  • Vợt một Lisp đánh máy
  • Clojure yêu thích của tôi, các ví dụ ở trên là mã Clojure. Một Lisp hiện đại chạy trên JVM. Có một số ví dụ về macro Clojure trên SO (nhưng đây không phải là nơi thích hợp để bắt đầu. Tôi sẽ xem xét 4clojure , braveclojure hoặc clojure koans )).

Oh và nhân tiện, Lisp có nghĩa là Xử lý LISt.

Về ví dụ của bạn

Tôi sẽ đưa ra ví dụ bằng cách sử dụng Clojure dưới đây:

Nếu bạn có thể viết một addhàm trong Clojure (defn add [a b] ...your-implementation-here... ), bạn có thể đặt tên +như vậy (defn + [a b] ...your-implementation-here... ). Trên thực tế đây là những gì được thực hiện trong triển khai thực tế (phần thân của hàm có liên quan nhiều hơn một chút nhưng định nghĩa về cơ bản giống như tôi đã viết ở trên).

Còn ký hiệu infix thì sao? Vâng Clojure sử dụng prefixký hiệu (hoặc tiếng Ba Lan), vì vậy chúng ta có thể tạo một infix-to-prefixmacro có thể biến mã tiền tố thành mã Clojure. Điều này thực sự dễ dàng đáng ngạc nhiên (nó thực sự là một trong những bài tập vĩ mô trong công án clojure)! Nó cũng có thể được nhìn thấy trong tự nhiên, ví dụ, xem macro Incanter$= .

Đây là phiên bản đơn giản nhất từ ​​công án giải thích:

(defmacro infix [form]
  (list (second form) (first form) (nth form 2)))

;; takes a form (ie. some code) as parameter
;; and returns a list (ie. some other code)
;; where the first element is the second element from the original form
;; and the second element is the first element from the original form
;; and the third element is the third element from the original form (indexes start at 0)
;; example :
;; (infix (9 + 1))
;; will become (+ 9 1) which is valid Clojure code and will be executed to give 10 as a result

Để thúc đẩy điểm hơn nữa, một số trích dẫn Lisp :

Một phần của những gì làm cho Lisp đặc biệt là nó được thiết kế để phát triển. Bạn có thể sử dụng Lisp để xác định toán tử Lisp mới. Khi các khái niệm trừu tượng mới trở nên phổ biến (ví dụ lập trình hướng đối tượng), nó luôn trở nên dễ thực hiện chúng trong Lisp. Giống như DNA, một ngôn ngữ như vậy không lỗi thời.

- Paul Graham, Lisp thường gặp

Lập trình tại khu vực Lisp giống như chơi với các lực lượng nguyên thủy của vũ trụ. Nó cảm thấy như sét giữa các ngón tay của bạn. Không có ngôn ngữ nào khác thậm chí cảm thấy gần gũi.

- Glenn Ehrlich, Đường đến Lisp


1
Lưu ý rằng siêu lập trình, trong khi thú vị, không cần thiết để hỗ trợ những gì OP đang hỏi. Bất kỳ ngôn ngữ nào có hỗ trợ cho các chức năng hạng nhất là đủ.
Andres F.

1
Ví dụ cho câu hỏi của OP: (let ((opp # '+)) (in (áp dụng opp' (1 2))))
Kasper van den Berg

1
Không đề cập đến Lisp thường gặp?
coredump

3
Tôi nhớ trong một cuộc thảo luận về các ngôn ngữ, Ken đã nói về sự ưu tiên trong APL và kết luận với "Tôi hầu như không bao giờ sử dụng dấu ngoặc đơn!" Và ai đó từ khán giả hét lên, "đó là vì Dick đã sử dụng hết chúng!"
JDługosz

2
Trong ngôn ngữ kiểu C ++, bạn có thể thực hiện lại if, nhưng bạn cần phải bao bọc thenelsetranh luận với lambdas. PHP và JavaScript có function(), C ++ có lambdas và tồn tại một phần mở rộng của Apple thành C với lambdas.
Damian Yerrick

9

$test = $number1 $operator1 $number2 $operator2 $number3;

Hầu hết các ngôn ngữ triển khai đều có một bước trong đó trình phân tích cú pháp phân tích mã của bạn và xây dựng một cây từ nó. Vì vậy, ví dụ biểu thức 5 + 5 * 8sẽ được phân tích cú pháp như

  +
 / \
5   *
   / \
  8   8

nhờ kiến ​​thức của nhà soạn nhạc về các ưu tiên. Nếu bạn cung cấp cho nó các biến thay cho các toán tử, nó sẽ không biết thứ tự hoạt động thích hợp trước khi chạy mã. Đối với hầu hết các triển khai sẽ là một vấn đề nghiêm trọng, vì vậy hầu hết các ngôn ngữ không cho phép điều đó.

Tất nhiên bạn có thể hình dung một ngôn ngữ trong đó trình phân tích cú pháp chỉ phân tích cú pháp ở trên dưới dạng một chuỗi các biểu thức và toán tử, để được sắp xếp và đánh giá khi chạy. Có lẽ không có nhiều ứng dụng cho việc này.

Nhiều ngôn ngữ kịch bản cho phép đánh giá các biểu thức tùy ý (hoặc ít nhất là các biểu thức số học tùy ý như trong trường hợp expr) trong thời gian chạy. Ở đó bạn chỉ có thể kết hợp các số và toán tử của mình thành một biểu thức duy nhất và để ngôn ngữ đánh giá điều đó. Trong PHP (và nhiều người khác) chức năng đó được gọi eval.

$test = eval("$number1 $operator1 $number2 $operator2 $number3");

Ngoài ra còn có các ngôn ngữ cho phép tạo mã tại thời điểm biên dịch. Các biểu hiện mixin trong D nói đến tâm trí của tôi, nơi tôi tin rằng bạn có thể viết một cái gì đó như

test = mixin("number1 " + operator1 + " number2 " + operator2 + "number3");

Ở đây operator1operator2sẽ phải là các hằng chuỗi được biết đến tại thời điểm biên dịch, ví dụ như các tham số mẫu. number1, number2number3bị bỏ như các biến thời gian chạy bình thường.

Các câu trả lời khác đã thảo luận về các cách khác nhau về cách một toán tử và một hàm giống nhau ít nhiều giống nhau, tùy thuộc vào ngôn ngữ. Nhưng thông thường, có một sự khác biệt cú pháp giữa một biểu tượng toán tử infix dựng sẵn như +và một tên gọi có thể gọi được operator1. Tôi sẽ để lại chi tiết cho những câu trả lời khác.


+1 Bạn nên bắt đầu với "có thể có trong PHP với eval()cấu trúc ngôn ngữ " ... Về mặt kỹ thuật, nó cung cấp chính xác kết quả mong muốn được hỏi trong câu hỏi.
Armfoot

@Armfoot: Thật khó để biết trọng tâm của câu hỏi nằm ở đâu. Tiêu đề nhấn mạnh vào biến số của kiểu toán tử khía cạnh, và evalkhông trả lời khía cạnh đó, vì đối với evalcác toán tử chỉ là các chuỗi. Do đó, tôi đã bắt đầu với một lời giải thích về lý do tại sao các biến được nhập bởi toán tử sẽ gây ra vấn đề, trước khi tôi bắt đầu thảo luận về các lựa chọn thay thế.
MvG

Tôi hiểu điểm của bạn, nhưng hãy xem xét rằng bằng cách nhét mọi thứ vào một chuỗi, về cơ bản bạn ngụ ý rằng các loại biến không còn phù hợp nữa (vì PHP đã được sử dụng để minh họa, đây dường như là trọng tâm của câu hỏi), và cuối cùng, bạn có thể đặt chúng theo cùng một cách như thể một số biến đó thuộc "toán tử loại" trong khi nhận được kết quả tương tự ... Đó là lý do tại sao tôi tin rằng đề xuất của bạn cung cấp câu trả lời chính xác nhất cho câu hỏi.
Armfoot

2

Algol 68 có chính xác tính năng đó. Ví dụ của bạn trong Algol 68 sẽ như thế này:

số int1 = 5;                              ¢ (Type 'Int') ¢
op operator1 = int ( int một , b ) một + b ; ¢ (Gõ không tồn tại 'điều hành') ¢
PRIO operator1 = 4;
int số2 = 5;                              ¢ (Type 'Int') ¢
op operator2 = int ( int một , b ) một * b ;  ¢(Gõ không tồn tại 'điều hành') ¢
PRIO operator2 = 5;
số int3 = 8;                              ¢ (Type 'Int') ¢

int test = number1 operator1 number2 operator2 number3 ; ¢ 5 + 5 * 8. ¢

var_dump ( kiểm tra );

Ví dụ thứ hai của bạn sẽ như thế này:

số int4 = 9;
op toán tử3 = bool ( int a , b ) a < b ; toán tử tiên
tiến3 = 3; nếu number1 $ operator3 number4 sau đó ¢ 5 <9 (true) ¢ in ( đúng ) fi


Bạn sẽ lưu ý rằng các ký hiệu toán tử được xác định và gán các thân phương thức có chứa thao tác mong muốn. Các toán tử và toán hạng của chúng đều được gõ và các toán tử có thể được chỉ định mức độ ưu tiên để việc đánh giá sẽ diễn ra theo đúng thứ tự. Bạn cũng có thể nhận thấy rằng có một số khác biệt nhỏ về phông chữ giữa ký hiệu toán tử và ký hiệu biến.

Trên thực tế, mặc dù ngôn ngữ được viết bằng fonting, các máy trong ngày có thể không xử lý các phông chữ (băng giấy và thẻ đấm), và stropping được sử dụng. Chương trình có thể sẽ được nhập vào như:

'INT' NUMBER4 = 9;
'OP' 'OPERATOR3' = 'BOOL' ('INT' A,B) A < B;
'PRIO' 'OPERATOR3' = 3;
'IF' NUMBER1 'OPERATOR3' NUMBER4 'THEN' 'C' 5 < 9 'C'
PRINT('TRUE')
'FI'

Bạn cũng có thể chơi các trò chơi thú vị với ngôn ngữ khi bạn có thể xác định các biểu tượng của riêng mình cho các nhà khai thác, điều mà tôi đã khai thác một lần, nhiều năm trước ... [2].


Người giới thiệu:

[1] Giới thiệu không chính thức về Algol 68 của CHLindsey và SG van der Meulen, Bắc Hà Lan, 1971 .

[2] Algol 68 Cụm từ, Một công cụ hỗ trợ trình biên dịch viết bằng Algol 68 , BC Tompsett, Hội nghị quốc tế về các ứng dụng của Algol 68, tại Đại học East Anglia, Norwich, UK, 1976 ..

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.