Czy operatorzy są bardziej czytelni niż słowa kluczowe lub funkcje? [Zamknięte]

14

Jest to nieco subiektywne, ale mam nadzieję, że lepiej zrozumiem, jakie czynniki sprawiają, że operator jest łatwy w użyciu w porównaniu z tępym i trudnym. Ostatnio zastanawiam się nad projektami językowymi, a jednym z problemów, nad którymi zawsze się zastanawiam, jest to, kiedy wykonać kluczową operację w języku jako operator, a kiedy użyć słowa kluczowego lub funkcji.

Haskell jest z tego powodu znany, ponieważ niestandardowe operatory są łatwe do utworzenia, a często nowy typ danych jest dostarczany z kilkoma operatorami do użytku na nim. Na przykład biblioteka Parsec zawiera mnóstwo operatorów do łączenia parserów, z podobnymi klejnotami >.i .> nawet nie pamiętam, co mają na myśli, ale pamiętam, że bardzo łatwo z nimi pracowałem, kiedy już zapamiętałem, z czym mają na myśli. Czy takie wywołanie funkcji leftCompose(parser1, parser2)byłoby lepsze? Z pewnością bardziej gadatliwy, ale pod pewnymi względami jaśniejszy.

Przeciążenie operatorów w językach podobnych do C jest podobnym problemem, ale jest połączone z dodatkowym problemem przeciążenia znaczenia znanych operatorów, takich jak +nowe niezwykłe znaczenia.

W każdym nowym języku wydaje się to dość trudnym problemem. Na przykład w F #, casting używa matematycznie wyprowadzonego operatora rzutowania, zamiast składni rzutowania w stylu C # lub pełnego stylu VB. C #: (int32) xVB: CType(x, int32)F #:x :> int32

Teoretycznie nowy język może mieć operatory dla większości wbudowanych funkcji. Zamiast deflub declub vardo deklaracji zmiennej, dlaczego nie ! namelub @ nameczy coś podobnego. Z pewnością skraca deklarację, po której następuje wiązanie: @x := 5zamiast declare x = 5lub let x = 5 większość kodu będzie wymagała wielu definicji zmiennych, więc dlaczego nie?

Kiedy operator jest przejrzysty i użyteczny, a kiedy zasłania?

CodexArcanum
źródło
1
Chciałbym, aby jeden z 1000 programistów, którzy kiedykolwiek skarżyli się na brak przeciążenia operatora Javy, odpowiedziałby na to pytanie.
smp7d,
1
Myślałem o APL, kiedy skończyłem pisać pytanie, ale w pewien sposób wyjaśnia to punkt i przenosi go na nieco mniej subiektywne terytorium. Myślę, że większość ludzi może zgodzić się z tym, że APL traci łatwość obsługi dzięki ogromnej liczbie operatorów, ale liczba osób, które narzekają, gdy język nie ma przeciążenia operatora, z pewnością przemawia za tym, że niektóre funkcje są dobrze dostosowane działają jako operatorzy. Pomiędzy zabronionymi operatorami niestandardowymi a niczym innym, oprócz operatorów, należy ukryć pewne wskazówki dotyczące praktycznej implementacji i użytkowania.
CodexArcanum
Moim zdaniem brak przeciążenia operatora jest budzący zastrzeżenia, ponieważ uprzywilejowuje typy rodzime nad typami zdefiniowanymi przez użytkownika (zauważ, że Java robi to również na inne sposoby). Jestem znacznie bardziej ambiwalentny co do niestandardowych operatorów w stylu Haskell, które wydają się być otwartym zaproszeniem do kłopotów ...
nadchodząca burza
2
@comingstorm: Myślę, że sposób Haskella jest lepszy. Kiedy masz skończony zestaw operatorów, które możesz przeciążać na różne sposoby, często jesteś zmuszony do ponownego użycia operatorów w różnych kontekstach (np. +Do konkatenacji łańcuchów lub <<do strumieni). Z Haskell, z drugiej strony, operator jest albo po prostu funkcja o nazwie użytkownika (czyli nie przeciążony) lub częścią klasy typu co oznacza, że podczas, gdy jest polimorficzny, to robi to samo logiczną rzeczą dla każdego typu i ma nawet ten sam typ podpisu. Tak >>jest w >>przypadku każdego typu i nigdy nie będzie trochę przesunięcia.
Tikhon Jelvis,

Odpowiedzi:

16

z ogólnego punktu widzenia projektowania języka nie ma żadnej różnicy między funkcjami a operatorami. Można opisać funkcje jako operacje przedrostkowe z dowolną (lub nawet zmienną) dowolnością. I słowa kluczowe mogą być postrzegane jako funkcje lub operatory z zastrzeżonymi nazwami (co jest przydatne w projektowaniu gramatyki).

Wszystkie te decyzje ostatecznie sprowadzają się do tego, jak chcesz czytać notację i że, jak mówisz, jest subiektywne, chociaż można dokonać pewnych oczywistych racjonalizacji, np. Użyć zwykłych operatorów poprawek do matematyki, tak jak wszyscy ją znają.

Wreszcie Dijkstra napisał interesujące uzasadnienie zastosowanych notacji matematycznych , które obejmuje dobrą dyskusję na temat kompromisów notacji infiksowych i notacji prefiksowych

jk.
źródło
4
Chciałbym dać ci drugą +1 za link do artykułu Dijkstry.
AProgrammer
Świetny link, musiałeś opublikować konto PayPal! ;)
Adriano Repetti,
8

Dla mnie operator przestaje być przydatny, gdy nie możesz już czytać wiersza kodu na głos lub w głowie, i ma to sens.

Na przykład declare x = 5czyta jako "declare x equals 5"lub let x = 5może być czytany jako "let x equal 5", co jest bardzo zrozumiałe, gdy jest czytane na głos, jednak czytanie @x := 5jest czytane jako "at x colon equals 5"(lub "at x is defined to be 5"jeśli jesteś matematykiem), co w ogóle nie ma sensu.

Więc moim zdaniem, użyj operatora, jeśli kod może być odczytany na głos i zrozumiany przez rozsądnie inteligentną osobę nie znającą języka, ale użyj nazw funkcji, jeśli nie.

Rachel
źródło
5
Nie wydaje mi się, żeby @x := 5trudno było to rozgryźć - wciąż czytam to sobie na głos jako set x to be equal to 5.
FrustratedWithFormsDesigner
3
@Rachel w takim przypadku :=ma szersze zastosowanie w notacji matematycznej poza językami programowania. Również stwierdzenia takie x = x + 1stają się nieco absurdalne.
ccoakley,
3
Jak na ironię, zapomniałem, że niektórzy ludzie nie uznaliby :=za zadanie. Kilka języków faktycznie tego używa. Myślę, że Smalltalk jest chyba najbardziej znanym. To, czy przypisanie i równość powinny być oddzielnymi operatorami, to zupełnie inna puszka robaków, z których jedna prawdopodobnie została już objęta innym pytaniem.
CodexArcanum
1
@ccoakley Dzięki, nie wiedziałem, że :=był używany w matematyce. Znalazłem dla niej jedną definicję „zdefiniowano jako”, więc @x := 5zostałbym przetłumaczony na angielski "at x is defined to be 5", na co nadal nie mam tyle sensu, ile powinien.
Rachel
1
Z pewnością Pascal użył: = jako przydziału, z powodu braku lewej strzałki w ASCII.
Vatine
4

Operator jest przejrzysty i przydatny, gdy jest znany. A to prawdopodobnie oznacza, że ​​przeciążanie operatorów powinno być wykonywane tylko wtedy, gdy wybrany operator jest wystarczająco blisko (jak w formie, pierwszeństwie i asocjatywności) jako już ustalona praktyka.

Ale kopiąc trochę dalej, są dwa aspekty.

Składnia (przedrostek dla operatora w przeciwieństwie do przedrostka dla składni wywołania funkcji) i nazewnictwo.

W przypadku składni istnieje inny przypadek, w którym infix jest na tyle wyraźniejszy niż prefiks, że może uzasadnić wysiłek zapoznania się: gdy połączenia są łączone w łańcuchy i zagnieżdżane.

Porównaj a * b + c * d + e * fz +(+(*(a, b), *(c, d)), *(e, f))lub + + * a b * c d * e f(oba są parsowalne w tym samym stanie co wersja infix). Fakt, że +oddzielam terminy zamiast poprzedzać jeden z nich długą drogą, sprawia, że ​​jest on bardziej czytelny w moich oczach (ale musisz pamiętać zasady pierwszeństwa, które nie są potrzebne do składni prefiksu). Jeśli potrzebujesz połączyć to w ten sposób, długoterminowa korzyść będzie warta kosztów nauki. Zachowujesz tę przewagę, nawet jeśli nazywasz operatorów literami zamiast symboli.

Jeśli chodzi o nazewnictwo operatorów, widzę niewiele korzyści z używania czegoś innego niż ustalone symbole lub czysta nazwa. Tak, mogą być krótsze, ale są naprawdę tajemnicze i szybko zostaną zapomniane, jeśli nie masz dobrego powodu, aby je znać.

Trzecim aspektem, jeśli weźmiesz POV do projektowania języka, jest to, czy zestaw operatorów powinien być otwarty lub zamknięty - czy możesz dodać operatora, czy nie - oraz czy priorytet i powiązanie powinny być określone przez użytkownika, czy nie. Moim pierwszym podejściem byłaby ostrożność i nie zapewnianie priorytetu i powiązania określonego przez użytkownika, podczas gdy ja byłbym bardziej otwarty na posiadanie otwartego zestawu (zwiększyłoby to nacisk na użycie przeciążenia operatora, ale zmniejszyłoby ten, aby użyć złego aby skorzystać ze składni infix, tam gdzie jest to przydatne).

AProgrammer
źródło
Jeśli użytkownicy mogą dodawać operatory, dlaczego nie pozwolić im ustawić pierwszeństwa i powiązania dodanych operatorów?
Tikhon Jelvis,
@TikhonJelvis, To była głównie konserwatywność, ale są pewne aspekty do rozważenia, jeśli chcesz być w stanie użyć jej do czegoś innego niż przykłady. Na przykład, chcesz powiązać priorytet i skojarzenie z operatorem, a nie z danym elementem przeciążonego zestawu (wybrałeś element po zakończeniu parsowania, wybór nie może wpłynąć na parsowanie). Aby więc zintegrować biblioteki korzystające z tego samego operatora, muszą uzgodnić jego priorytet i powiązanie. Przed dodaniem możliwości spróbuję dowiedzieć się, dlaczego tak mało języków podążyło za Algolem 68 (brak AFAIK) i pozwoliłem na ich zdefiniowanie.
AProgrammer
Haskell pozwala definiować dowolne operatory i ustawiać ich pierwszeństwo i powiązanie. Różnica polega na tym, że nie można przeciążać operatorów per se - zachowują się one jak normalne funkcje. Oznacza to, że nie możesz mieć różnych bibliotek próbujących używać tego samego operatora do różnych rzeczy. Ponieważ możesz zdefiniować własnego operatora, jest o wiele mniej powodów, aby używać tych samych operatorów do różnych celów.
Tikhon Jelvis
1

Symbole-operatory są przydatne, gdy intuicyjnie mają sens. +i -oczywiście na przykład dodawanie i odejmowanie, dlatego prawie każdy język używa ich jako operatorów. To samo z porównania ( <i >uczy się w szkole, a <=i >=są intuicyjne rozszerzenia podstawowych porównań kiedy zrozumiesz, że nie ma porównania podkreślony-klawisze na standardowej klawiaturze.)

*i /są mniej oczywiste od razu, ale są one powszechnie stosowane, a ich pozycja na klawiaturze numerycznej tuż obok klawiszy +i -pomaga zapewnić kontekst. C <<i >>należą do tej samej kategorii: kiedy zrozumiesz, co to jest lewy i prawy shift, nie jest zbyt trudno zrozumieć mnemoniki za strzałkami.

Potem dochodzisz do niektórych naprawdę dziwnych rzeczy, które mogą zmienić kod C w nieczytelną zupę operatora. (Wybieram tutaj C, ponieważ jest to bardzo obciążający operator przykład, którego składnia jest prawie wszystkim znana, zarówno poprzez pracę z C, jak i języki potomne.) Na przykład %operator. Wszyscy wiedzą, co to jest: to znak procentu ... prawda? Z wyjątkiem C nie ma to nic wspólnego z dzieleniem przez 100; to operator modułu. To mnemonicznie ma zero sensu, ale jest!

Jeszcze gorzej są operatory logiczne. &jako i sens, chyba że istnieją dwa różne &podmioty, które zrobić dwie różne rzeczy, i oba są składniowo obowiązuje w każdym przypadku, gdy jeden z nich jest poprawny, choć tylko jeden z nich kiedykolwiek rzeczywiście ma sens w danym przypadku. To tylko przepis na zamieszanie! Operatory lub , xor i not są jeszcze gorsze, ponieważ nie używają mnemonicznie użytecznych symboli i też nie są spójne. (Brak operatora podwójnego xor , a logiczne i bitowe nie używają dwóch różnych symboli zamiast jednego symbolu i wersji podwójnej).

Żeby było jeszcze gorzej, operatory &i *są ponownie wykorzystywane do zupełnie innych rzeczy, co utrudnia analizowanie języka przez ludzi i kompilatory. (Co to A * Bznaczy? Zależy to całkowicie od kontekstu: jest Atypem lub zmienną?)

Trzeba przyznać, że nigdy nie rozmawiałem z Dennisem Ritchiem i nie pytałem go o decyzje projektowe, które podjął w tym języku, ale wydaje się, że filozofią stojącą za operatorami były „symbole ze względu na symbole”.

Porównaj to z Pascalem, który ma tych samych operatorów co C, ale inną filozofię ich reprezentowania: operatory powinny być intuicyjne i łatwe do odczytania. Operatory arytmetyczne są takie same. Operatorem modułu jest słowo mod, ponieważ na klawiaturze nie ma symbolu, który oczywiście oznaczałby „moduł”. Operatory logiczne są and, or, xori not, a nie tylko jednej z nich; kompilator wie, czy potrzebujesz wersji logicznej, czy bitowej w zależności od tego, czy operujesz na liczbach boolowych, czy liczbach, eliminując całą klasę błędów. To sprawia, że ​​kod Pascal jest znacznie łatwiejszy do zrozumienia niż kod C, szczególnie w nowoczesnym edytorze z podświetlaniem składni, dzięki czemu operatory i słowa kluczowe są wizualnie różne od identyfikatorów.

Mason Wheeler
źródło
4
Pascal wcale nie reprezentuje innej filozofii. Jeśli czytasz gazety Wirth, on używany symbole rzeczy, jak andi orprzez cały czas. Kiedy jednak opracował Pascala, zrobił to na komputerze mainframe Control Data, który używał znaków 6-bitowych. Zmusiło to decyzję do użycia słów do wielu rzeczy, z tego prostego powodu, że zestaw znaków po prostu nie miał prawie tyle miejsca na symbole i takie jak ASCII. Użyłem zarówno C, jak i Pascala, i stwierdziłem, że C jest znacznie bardziej czytelny. Może wolisz Pascala, ale to kwestia gustu, a nie faktu.
Jerry Coffin
Aby dodać do uwagi @ WerryCoffin o Wirth, jego kolejne języki (Modula, Oberon) używają więcej symboli.
AProgrammer
@AProgrammer: I nigdzie nie poszli. ;)
Mason Wheeler
Myślę |(i przez to ||) za lub ma sens. Cały czas widzisz to również jako alternatywę w innych kontekstach, takich jak gramatyka, i wygląda na to, że dzieli dwie opcje.
Tikhon Jelvis
1
Zaletą słów kluczowych opartych na literach jest to, że ich przestrzeń jest znacznie większa niż symbole. Na przykład język w stylu Pascala może zawierać operatory zarówno dla „modułu”, jak i „reszty” oraz dla podziału na liczby całkowite i zmiennoprzecinkowe (które mają różne znaczenia poza typami ich operandów).
supercat
1

Myślę, że operatory są znacznie bardziej czytelne, gdy ich funkcja ściśle odzwierciedla ich znajomy, matematyczny cel. Na przykład standardowe operatory, takie jak +, - itp., Są bardziej czytelne niż Dodawanie lub Odejmowanie podczas wykonywania dodawania i odejmowania. Zachowanie operatora musi być jasno określone. Mogę na przykład tolerować przeciążenie znaczeniem + dla konkatenacji listy. W idealnym przypadku operacja nie miałaby skutków ubocznych, zwracając wartość zamiast mutować.

Walczyłem jednak z operatorami skrótów dla funkcji takich jak fold. Oczywiście ułatwiłoby to z nimi więcej doświadczenia, ale uważam, że jest foldRightbardziej czytelne niż /:.

Matt H.
źródło
4
Może dotyczy to również częstotliwości używania. Nie sądzę fold, że wystarczająco często, że operator oszczędza dużo czasu. Być może dlatego LISPy (+ 1 2 3) wydaje się dziwny, ale (dodaj 1 2 3) mniej. Mamy tendencję do myślenia o operatorach jako ściśle binarnych. Operatory >>.stylu Parsec mogą być głupie, ale przynajmniej podstawową rzeczą, którą robisz w Parsec, jest łączenie parserów, więc operatory dla niego mają wiele zastosowania.
CodexArcanum
1

Problem z operatorami polega na tym, że jest ich tylko niewielka liczba w porównaniu z liczbą sensownych (!) Nazw metod, których można użyć zamiast tego. Efektem ubocznym jest to, że operatorzy definiowani przez użytkownika mają tendencję do wprowadzania wielu przeciążeń.

Z pewnością wiersz C = A * x + b * cłatwiej jest pisać i czytać niż C = A.multiplyVector(x).addVector(b.multiplyScalar(c)).

Lub, cóż, z pewnością łatwiej jest pisać, jeśli masz wszystkie przeciążone wersje wszystkich tych operatorów w twojej głowie. Możesz go później przeczytać, naprawiając przedostatni błąd.

Teraz, w przypadku większości kodu, który będzie tam działał, nie ma sprawy. „Projekt przechodzi wszystkie testy i działa” - dla wielu programów to wszystko, czego możemy chcieć.

Ale wszystko działa inaczej, gdy tworzysz oprogramowanie, które trafia do krytycznych obszarów. Krytyczne dla bezpieczeństwa rzeczy. Bezpieczeństwo. Oprogramowanie utrzymujące samoloty w powietrzu lub utrzymujące działanie obiektów jądrowych. Lub oprogramowanie, które szyfruje wysoce poufne wiadomości e-mail.

Dla ekspertów ds. Bezpieczeństwa specjalizujących się w audycie kodu może to być koszmar. Połączenie dużej liczby zdefiniowanych przez użytkownika operatorów i przeciążenia operatora może pozostawić ich w nieprzyjemnej sytuacji, w której trudno im będzie ustalić, jaki kod w końcu zostanie uruchomiony.

Tak więc, jak stwierdzono w pierwotnym pytaniu, jest to bardzo subiektywny temat. I choć wiele sugestii dotyczących sposobu korzystania z operatorów może brzmieć doskonale sensownie, połączenie tylko kilku z nich może na dłuższą metę przysporzyć wiele problemów.

doppelfish
źródło
0

Podejrzewam, że najbardziej ekstremalnym przykładem jest APL, następujący program to „gra życia”:

life←{↑1 ⍵∨.∧3 4=+/,¯1 0 1∘.⊖¯1 0 1∘.⌽⊂⍵}

A jeśli potrafisz zrozumieć, co to wszystko znaczy, nie spędzając kilku dni z podręcznikiem, powodzenia!

Problem polega na tym, że masz zestaw symboli, które prawie wszyscy intuicyjnie rozumieją: +,-,*,/,%,=,==,&

a pozostałe, które wymagają wyjaśnienia, zanim będą mogły być zrozumiane, i ogólnie są specyficzne dla konkretnego języka. Na przykład nie ma oczywistego symbolu określającego, który element tablicy chcesz, [], (), a nawet „.” zostały użyte, ale nie ma też oczywistego słowa kluczowego, którego można by łatwo użyć, dlatego należy rozważyć przemyślane użycie operatorów. Ale nie za dużo z nich.

James Anderson
źródło