Dlaczego istnieje tak niewiele języków z „operatorem” typu zmiennego?

47

Mam na myśli to w ten sposób:

<?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);
?>

Ale także w ten sposób:

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

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

Wydaje się, że nie ma tego w żadnym języku - czy jest dobry powód, dla którego go nie ma?

kgongonowdoe
źródło
28
Ogólnie rzecz biorąc, to, co chcesz zrobić, byłoby objęte wszystkimi językami, które obsługują pewną formę programowania meta z jakimś rodzajem lambdas, zamknięć lub anonimowych funkcji, które byłyby powszechnym sposobem implementacji takich funkcji. W przypadku języków, w których metody są pierwszorzędnymi obywatelami, można ich używać mniej więcej tak samo jak zmiennych. Chociaż nie w tej prostej składni, której tu używasz, ponieważ w większości takich języków należy wyjaśnić, że faktycznie chcesz wywołać metodę przechowywaną w zmiennej.
thorsten müller
7
@MartinMaat Języki funkcjonalne często to robią.
Thorbjørn Ravn Andersen
7
w haskell operatory są funkcjami jak każda inna funkcja. typ (+)to Num a => a -> a -> aIIRC. możesz również zdefiniować funkcje, aby można je było zapisać w postaci nieskorygowanej ( a + bzamiast (+) a b)
sara
5
@enderland: Twoja zmiana całkowicie zmieniła cel pytania. Zaczęło się od pytania, czy istnieją jakieś języki, od pytania, dlaczego tak mało istnieje. Myślę, że twoja edycja spowoduje wielu zdezorientowanych czytelników.
Bryan Oakley
5
@enderland: chociaż to prawda, całkowita zmiana tematu służy jedynie do pomylenia. Jeśli jest to nie na temat, społeczność zagłosuje za jego zamknięciem. Żadna z odpowiedzi (w chwili, gdy to piszę) nie ma sensu dla pytania w formie, w jakiej zostało napisane.
Bryan Oakley

Odpowiedzi:

104

Operatory to tylko funkcje o śmiesznych nazwach, z pewną specjalną składnią.

W wielu językach, tak różnorodnych jak C ++ i Python, można przedefiniować operatory, zastępując specjalne metody klasy. Następnie standardowe operatory (np. +) Działają zgodnie z logiką, którą podajesz (np. Łączenie łańcuchów lub dodawanie macierzy lub cokolwiek innego).

Ponieważ takie funkcje definiujące operator są tylko metodami, możesz przekazać je tak jak funkcję:

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

Inne języki pozwalają bezpośrednio definiować nowe operatory jako funkcje i używać ich w formie 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

Niestety, nie jestem pewien, czy PHP obsługuje coś takiego, i czy wsparcie byłoby dobrą rzeczą. Użyj prostej funkcji, jest bardziej czytelna niż $foo $operator $bar.

9000
źródło
2
@tac: Tak, jest to miłe i może być nawet przeniesione do innych języków :) Jeśli chodzi o Haskell, najbardziej tęsknię za tym działem, który jest $operatorem, który omija nawiasy (szczególnie wiele zagnieżdżonych) - ale to może działać tylko z -funkcje funkcyjne, wykluczając np. Python i Java. Kompozycja funkcji jednoargumentowej OTOH może być ładnie wykonana .
9000
6
Nigdy nie byłem fanem przeciążania operatora, ponieważ tak, operator jest tylko funkcją o specjalnej składni, ale istnieje domniemana umowa, która zwykle idzie z operatorami, która nie idzie w parze z funkcjami. Na przykład „+” ma pewne oczekiwania - pierwszeństwo operatora, przemienność itp. - a przeciwstawianie się tym oczekiwaniom jest pewną drogą do dezorientacji ludzi i generowania błędów. Jednym z powodów, dla których choć uwielbiam javascript, wolałbym, aby rozróżniały + dla dodawania i konkatenacji. Perl miał to właśnie tam.
fool4jesus
4
Zauważ, że Python ma standardowy operatormoduł, który pozwalałby Ci pisać action = operator.addi pozwalał na pracę z każdym typem, który definiuje +(nie tylko int).
dan04
3
W Haskell +działa na typie Num, więc możesz zaimplementować +każdy nowy typ danych, który tworzysz, ale w taki sam sposób, jak robisz cokolwiek innego, np. Fmap dla funktora. To jest tak naturalne dopasowanie dla Haskell, aby umożliwić przeciążenie operatora, że ​​będą musieli ciężko pracować, aby na to nie pozwolić!
Martin Capodici
17
Nie jestem pewien, dlaczego ludzie są nastawieni na przeciążenie. Pierwotne pytanie nie dotyczy przeciążenia. Wydaje mi się, że chodzi o operatorów jako wartości pierwszej klasy. Aby napisać, $operator1 = +a następnie użyć wyrażenia, nie trzeba wcale przeciążać operatora !
Andres F.
16

Istnieje wiele języków, które pozwalają na pewien rodzaj metaprogramowania . W szczególności jestem zaskoczony brakiem odpowiedzi na temat rodziny języków Lisp .

Z wikipedii:

Metaprogramowanie to pisanie programów komputerowych z możliwością traktowania programów jako ich danych.

Później w tekście:

Lisp jest prawdopodobnie kwintesencją języka z narzędziami do metaprogramowania, zarówno ze względu na historyczne pierwszeństwo, jak i ze względu na prostotę i siłę jego metaprogramowania.

Języki Lisp

Następuje szybkie, zmyślone wprowadzenie do Lisp.

Jednym ze sposobów na zobaczenie kodu jest zestaw instrukcji: zrób to, następnie zrób to, a następnie zrób jeszcze jedną rzecz ... To jest lista! Lista rzeczy do zrobienia dla programu. I oczywiście możesz mieć listy wewnątrz list do reprezentowania pętli i tak dalej ...

Jeśli będziemy reprezentować listę zawierającą elementy a, b, c, d tak: (ABCD) otrzymujemy coś, co wygląda jak Lisp wywołania funkcji, gdzie ajest funkcja, i b, c, dsą argumenty. Jeśli faktem jest typowy „Hello World!” program można napisać tak:(println "Hello World!")

Oczywiście b, club dmogą to być listy, które również coś oceniają. Następujące: (println "I can add :" (+ 1 3) )wydrukuje „„ Mogę dodać: 4 ”.

Tak więc program to seria zagnieżdżonych list, a pierwszy element to funkcja. Dobra wiadomość jest taka, że ​​możemy manipulować listami! Możemy więc manipulować językami programowania.

Zaleta Lisp

Lisps to nie tyle języki programowania, co zestaw narzędzi do tworzenia języków programowania. Programowalny język programowania.

W Lisps jest to nie tylko łatwiejsze do tworzenia nowych operatorów, ale także prawie niemożliwe jest napisanie niektórych operatorów w innych językach, ponieważ argumenty są oceniane po przekazaniu do funkcji.

Na przykład w języku podobnym do C powiedzmy, że chcesz sam napisać ifoperator, coś w stylu:

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

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

W takim przypadku oba argumenty zostaną ocenione i wydrukowane, w kolejności zależnej od kolejności oceny argumentów.

W Lisps pisanie operatora (nazywamy to makro) i pisanie funkcji jest mniej więcej tym samym i jest używane w ten sam sposób. Główną różnicą jest to, że parametry makra nie są oceniane przed przekazaniem ich jako argumentów do makra. Jest to niezbędne, aby móc napisać niektóre operatory, takie jak ifpowyższe.

Prawdziwe języki

Pokazuje, jak dokładnie jest to trochę poza zasięgiem, ale zachęcam do wypróbowania programowania w jednym Lisp, aby dowiedzieć się więcej. Na przykład możesz rzucić okiem na:

  • Schemat , stara, dość „czysta” Lisp z małym rdzeniem
  • Common Lisp, większy Lisp z dobrze zintegrowanym systemem obiektowym i wieloma implementacjami (jest standaryzowany przez ANSI)
  • Ruszaj na maszynie Lisp
  • Clojure mój ulubiony, powyższe przykłady to kod Clojure. Nowoczesny Lisp działający na JVM. Istnieją również przykłady makr Clojure na SO (ale nie jest to właściwe miejsce na początek. Na początku przyjrzałbym się 4clojure , braveclojure lub clojure koans ).

A tak przy okazji, Lisp oznacza LISt Processing.

Jeśli chodzi o twoje przykłady

Podam przykłady przy użyciu Clojure poniżej:

Jeśli możesz napisać addfunkcję w Clojure (defn add [a b] ...your-implementation-here... ), możesz ją +tak nazwać (defn + [a b] ...your-implementation-here... ). To właśnie dzieje się w rzeczywistej implementacji (treść funkcji jest nieco bardziej zaangażowana, ale definicja jest zasadniczo taka sama, jak napisałem powyżej).

Co z notacją infix? Cóż, Clojure używa prefixnotacji (lub polskiej), więc możemy stworzyć infix-to-prefixmakro, które zamienia prefiks w kod Clojure. Co jest w rzeczywistości zaskakująco łatwe (w rzeczywistości jest to jedno z ćwiczeń makro w kojojach clojure)! Można go również zobaczyć na wolności, na przykład zobacz makro Incanter$= .

Oto wyjaśniona najprostsza wersja z koanów:

(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

Aby jeszcze bardziej rozwinąć sprawę , niektóre cytaty Lisp :

„Częścią wyróżniającą Lisp jest to, że został zaprojektowany z myślą o ewolucji. Możesz użyć Lisp do zdefiniowania nowych operatorów Lisp. Gdy nowe abstrakcje stają się popularne (na przykład programowanie obiektowe), zawsze łatwo jest je zaimplementować w Lisp. Podobnie jak DNA, taki język nie wychodzi z mody. ”

- Paul Graham, ANSI Common Lisp

„Programowanie w Lisp jest jak zabawa z pierwotnymi siłami wszechświata. Czuje się jak błyskawica między opuszkami palców. Żaden inny język nie jest nawet blisko. ”

- Glenn Ehrlich, Road to Lisp

nha
źródło
1
Zauważ, że metaprogramowanie, chociaż interesujące, nie jest konieczne do poparcia tego, o co prosi OP. Wystarczy dowolny język z obsługą pierwszorzędnych funkcji.
Andres F.
1
Przykład pytania OP: (let ((opp # '+))) (print (Apply opp' (1 2))))
Kasper van den Berg
1
Brak wzmianki o Common Lisp?
coredump
3
Pamiętam podczas dyskusji panelowej na temat języków, Ken mówił o pierwszeństwie w APL i zakończył słowami: „W ogóle nie używam nawiasów!” I ktoś z publiczności krzyknął: „To dlatego, że Dick wykorzystał je wszystkie!”
JDługosz
2
W języku w stylu C ++ można zaimplementować ponownie if, ale należy zawinąć argumenty theni za elsepomocą lambdas. PHP i JavaScript mają function(), C ++ ma lambdas i istnieje rozszerzenie Apple do C z lambdas.
Damian Yerrick
9

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

Większość implementacji języków ma krok, w którym analizator składni analizuje kod i buduje z niego drzewo. Na przykład wyrażenie 5 + 5 * 8zostanie sparsowane jako

  +
 / \
5   *
   / \
  8   8

dzięki wiedzy kompilatora na temat pierwszeństwa. Jeśli podasz zmienne zamiast operatorów, przed uruchomieniem kodu nie znałby właściwej kolejności operacji. W przypadku większości implementacji byłby to poważny problem, więc większość języków na to nie pozwala.

Można oczywiście wymyślić język, w którym analizator składni po prostu analizuje powyższe jako sekwencję wyrażeń i operatorów, które mają być sortowane i oceniane w czasie wykonywania. Prawdopodobnie nie ma na to zbyt wiele zastosowań.

Wiele języków skryptowych pozwala na ocenę dowolnych wyrażeń (lub przynajmniej dowolnych wyrażeń arytmetycznych jak w przypadku expr) w czasie wykonywania. Tam możesz po prostu połączyć swoje liczby i operatory w jedno wyrażenie i pozwolić językowi to ocenić. W PHP (i wielu innych) ta funkcja jest wywoływana eval.

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

Istnieją również języki, które pozwalają na generowanie kodu w czasie kompilacji. Wyrażenie mixin D przychodzi mi do głowy, gdzie wierzę można napisać coś podobnego

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

Tutaj operator1i operator2musiałyby istnieć stałe ciągów, które są znane w czasie kompilacji, np. Parametry szablonu. number1, number2I number3pozostawiano w normalnych zmiennych wykonawczych.

Inne odpowiedzi już omawiały różne sposoby, w jaki operator i funkcja są mniej więcej tym samym, w zależności od języka. Ale zwykle istnieje różnica składniowa między wbudowanym symbolem operatora infix jak +i nazwanym podobnym do wywołania operator1. Zostawię szczegóły tym innym odpowiedziom.

MvG
źródło
+1 Powinieneś zacząć od „jest to możliwe w PHP z eval()konstrukcją językową ” ... Technicznie zapewnia dokładnie pożądany wynik zadany w pytaniu.
Armfoot
@Armfoot: Trudno powiedzieć, na czym polega pytanie. Tytuł podkreśla aspekt „zmiennej typu operator” i evalnie odpowiada na ten aspekt, ponieważ dla evaloperatorów są to tylko ciągi znaków. Dlatego zacząłem od wyjaśnienia, dlaczego zmienne typu operator powodują problemy, zanim zacznę omawiać alternatywy.
MvG
Rozumiem twoje argumenty, ale uważam, że poprzez upychanie wszystkiego w łańcuch, zasadniczo sugerujesz, że typy zmiennych nie są już istotne (ponieważ PHP zostało użyte do zilustrowania, wydaje się, że to jest sedno pytania), a na koniec mogę umieścić je w taki sam sposób, jakby niektóre z tych zmiennych były „operatorem typu”, jednocześnie uzyskując ten sam wynik ... Dlatego uważam, że twoja sugestia zapewnia najdokładniejszą odpowiedź na pytanie.
Armfoot
2

Algol 68 miał dokładnie tę funkcję. Twój przykład w Algolu 68 wyglądałby tak:

liczba całkowita 1 = 5;                              ¢ (Wpisz „Int”) ¢
op operator1 = int ( int a , b ) a + b ; ¢ (Wpisz nieistniejący „Operator”) ¢ operator
prio1 = 4; Int number2 = 5;                              ¢ (Wpisz „Int”) ¢ op operator2 = int ( int a , b ) a * b ;  ¢

(Wpisz nieistniejący „Operator”) ¢ operator
prio2 = 5; liczba int 3 = 8;                              ¢ (Wpisz „Int”) ¢

Int Test = number1 operator1 number2 operator2 number3 ; ¢ 5 + 5 * 8. ¢

var_dump ( test );

Twój drugi przykład wyglądałby tak:

liczba całkowita 4 = 9; operator
op 3 = bool ( int a , b ) a < b ; operator prio3 = 3; czy liczba1 $ operator3 number4 następnie ¢ 5 <9 (true) ¢ print ( prawda ) fi



Zauważysz, że symbole operatora są zdefiniowane i przypisane ciała metod, które zawierają żądaną operację. Operatory i ich operand są wpisane na maszynie, a operatorom można przypisać priorytety, aby ocena przebiegała w prawidłowej kolejności. Można również zauważyć, że istnieje niewielka różnica w czcionkach między symbolem operatora a symbolem zmiennej.

W rzeczywistości, chociaż język jest pisany przy użyciu czcionek, maszyny dnia nie były w stanie poradzić sobie z czcionkami (taśma papierowa i karty dziurkowane) i zastosowano stropowanie . Program prawdopodobnie zostanie wprowadzony jako:

'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'

Możesz także grać w ciekawe gry z tym językiem, w którym możesz zdefiniować własne symbole operatorów, które wykorzystałem raz, wiele lat temu ... [2].


Bibliografia:

[1] Nieformalne wprowadzenie do Algolu 68 autorstwa CHLindsey i SG van der Meulen, Holandia Północna, 1971 .

[2] Algol 68 Phrases, narzędzie wspomagające pisanie kompilatorów w Algolu 68 , BC Tompsett, Międzynarodowa konferencja nt. Zastosowań Algolu 68, na University of East Anglia, Norwich, Wielka Brytania, 1976 .

Brian Tompsett - 汤 莱恩
źródło