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) x
VB: CType(x, int32)
F #:x :> int32
Teoretycznie nowy język może mieć operatory dla większości wbudowanych funkcji. Zamiast def
lub dec
lub var
do deklaracji zmiennej, dlaczego nie ! name
lub @ name
czy coś podobnego. Z pewnością skraca deklarację, po której następuje wiązanie: @x := 5
zamiast declare x = 5
lub 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?
źródło
+
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.Odpowiedzi:
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
źródło
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 = 5
czyta jako"declare x equals 5"
lublet x = 5
może być czytany jako"let x equal 5"
, co jest bardzo zrozumiałe, gdy jest czytane na głos, jednak czytanie@x := 5
jest 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.
źródło
@x := 5
trudno było to rozgryźć - wciąż czytam to sobie na głos jakoset x to be equal to 5
.:=
ma szersze zastosowanie w notacji matematycznej poza językami programowania. Również stwierdzenia takiex = x + 1
stają się nieco absurdalne.:=
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.:=
był używany w matematyce. Znalazłem dla niej jedną definicję „zdefiniowano jako”, więc@x := 5
zostałbym przetłumaczony na angielski"at x is defined to be 5"
, na co nadal nie mam tyle sensu, ile powinien.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 * f
z+(+(*(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).
źródło
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 toA * B
znaczy? Zależy to całkowicie od kontekstu: jestA
typem 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
,xor
inot
, 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.źródło
and
ior
przez 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.|
(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.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
foldRight
bardziej czytelne niż/:
.źródło
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.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.
źródło
Podejrzewam, że najbardziej ekstremalnym przykładem jest APL, następujący program to „gra życia”:
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.
źródło