Gdybym chciał porównać dwie liczby (lub inne dobrze uporządkowane byty), zrobiłbym to z x < y
. Jeśli chcę porównać trzy z nich, licealistka z algebry zasugeruje spróbowanie x < y < z
. Programista we mnie odpowie wtedy „nie, to nieważne, musisz to zrobić x < y && y < z
”.
Większość języków, z którymi się spotkałem, nie obsługuje tej składni, co jest dziwne, biorąc pod uwagę, jak często występuje w matematyce. Python jest godnym uwagi wyjątkiem. JavaScript wygląda jak wyjątek, ale tak naprawdę jest to po prostu niefortunny produkt uboczny pierwszeństwa operatora i niejawnych konwersji; w node.js, 1 < 3 < 2
ocenia true
, ponieważ tak naprawdę jest (1 < 3) < 2 === true < 2 === 1 < 2
.
Moje pytanie brzmi zatem: dlaczego x < y < z
nie jest powszechnie dostępne w językach programowania z oczekiwaną semantyką?
static bool IsInRange<T>(this T candidate, T lower, T upper) where T : IComparable<T>
jeśli naprawdę przeszkadza ci zobaczyć&&
s)Odpowiedzi:
Są to operatory binarne, które po połączeniu łańcuchowym normalnie i naturalnie wytwarzają abstrakcyjne drzewo składniowe, takie jak:
Po dokonaniu oceny (co robisz od początku do końca), daje to wynik boolowski
x < y
, a następnie pojawia się błąd typuboolean < z
. Abyx < y < z
działać zgodnie z opisem, musisz utworzyć specjalny przypadek w kompilatorze, aby utworzyć drzewo składniowe, takie jak:Nie to, że nie można tego zrobić. Oczywiście jest, ale dodaje złożoności parserowi dla sprawy, która tak naprawdę nie pojawia się tak często. Zasadniczo tworzysz symbol, który czasami działa jak operator binarny, a czasem skutecznie działa jak operator trójskładnikowy, ze wszystkimi implikacjami obsługi błędów i takimi, które się z tym wiążą. Daje to dużo miejsca na rzeczy, które mogą się nie udać, których projektanci języka woleliby raczej uniknąć, jeśli to możliwe.
źródło
x<y<z
środki, lub co ważniejszex<y<=z
. Ta odpowiedź interpretujex<y<z
jako operator trójkowy. Właśnie tak nie należy interpretować tego dobrze zdefiniowanego wyrażenia matematycznego.x<y<z
jest zamiast tego skrótem(x<y)&&(y<z)
. Poszczególne porównania są wciąż binarne.Dlaczego
x < y < z
nie jest powszechnie dostępny w językach programowania?W tej odpowiedzi stwierdzam, że
Wprowadzenie
Potrafię mówić na ten temat z perspektywy Pythona. Jestem użytkownikiem języka z tą funkcją i lubię studiować szczegóły implementacji tego języka. Poza tym jestem nieco zaznajomiony z procesem zmiany języków, takich jak C i C ++ (norma ISO jest regulowana przez komitet i wersjonowana z roku na rok). Widziałem, jak zarówno Ruby, jak i Python wdrażają przełomowe zmiany.
Dokumentacja i implementacja Pythona
Z dokumentacji / gramatyki wynika, że możemy połączyć dowolną liczbę wyrażeń za pomocą operatorów porównania:
a dokumentacja zawiera ponadto:
Równoważność logiczna
Więc
logicznie równoważne pod względem oceny
x
,y
orazz
z tym wyjątkiem, żey
jest wyznaczana podwójnie:Znowu różnica polega na tym, że y jest oceniane tylko raz
(x < y <= z)
.(Uwaga: nawiasy są całkowicie niepotrzebne i zbędne, ale użyłem ich dla korzyści pochodzących z innych języków, a powyższy kod jest całkiem legalnym Pythonem).
Sprawdzanie przeanalizowanego abstrakcyjnego drzewa składni
Możemy sprawdzić, w jaki sposób Python analizuje łańcuchowe operatory porównania:
Widzimy więc, że tak naprawdę nie jest to trudne do przeanalizowania przez Python ani żaden inny język.
I w przeciwieństwie do obecnie przyjętej odpowiedzi, operacja trójskładnikowa jest ogólną operacją porównania, która wymaga pierwszego wyrażenia, iteracji określonych porównań i iteracji węzłów wyrażeń w celu oceny w razie potrzeby. Prosty.
Wnioski dotyczące Pythona
Osobiście uważam, że semantyka zakresu jest dość elegancka, a większość specjalistów Pythona, których znam, zachęciłaby do korzystania z tej funkcji, zamiast uważać ją za szkodliwą - semantyka jest dość wyraźnie określona w dobrze znanej dokumentacji (jak wspomniano powyżej).
Zauważ, że kod jest odczytywany znacznie więcej niż jest napisany. Zmiany, które poprawiają czytelność kodu, powinny zostać uwzględnione, a nie zdyskontowane poprzez podniesienie ogólnych spektrów lęku, niepewności i wątpliwości .
Dlaczego więc x <y <z nie jest powszechnie dostępny w językach programowania?
Myślę, że istnieje zbieżność przyczyn, które koncentrują się wokół względnej ważności cechy i względnego pędu / bezwładności zmiany dozwolonej przez gubernatorów języków.
Podobne pytania można zadać na temat innych ważniejszych funkcji językowych
Dlaczego wielokrotne dziedziczenie nie jest dostępne w Javie lub C #? Nie ma dobrej odpowiedzi, żeby którekolwiek z tych pytań . Być może deweloperzy byli zbyt leniwi, jak twierdzi Bob Martin, a podane powody są jedynie wymówką. Wielokrotne dziedziczenie jest dość dużym tematem w informatyce. Jest to z pewnością ważniejsze niż tworzenie łańcuchów przez operatora.
Istnieją proste obejścia
Łańcuch operatorów porównania jest elegancki, ale w żadnym wypadku nie jest tak ważny jak wielokrotne dziedziczenie. I podobnie jak Java i C # mają interfejsy jako obejście, tak też każdy język dla wielu porównań - po prostu łączysz porównania z logicznymi „i”, które działają wystarczająco łatwo.
Większość języków jest regulowana przez komitet
Większość języków ewoluuje według komisji (zamiast mieć rozsądnego Dobrotliwego Dyktatora Życia, tak jak Python). I spekuluję, że ta kwestia po prostu nie spotkała się z wystarczającym poparciem, aby wyjść z odpowiednich komisji.
Czy języki, które nie oferują tej funkcji, mogą się zmienić?
Jeśli język na to pozwala
x < y < z
bez oczekiwanej semantyki matematycznej, byłaby to przełomowa zmiana. Gdyby przede wszystkim na to nie pozwalał, dodanie tego byłoby prawie trywialne.Przełamywanie zmian
Jeśli chodzi o języki z przełomowymi zmianami: aktualizujemy języki z przełomowymi zmianami zachowania - ale użytkownikom się to nie podoba, szczególnie użytkownikom funkcji, które mogą być zepsute. Jeśli użytkownik polega na poprzednim zachowaniu
x < y < z
, prawdopodobnie głośno zaprotestowałby. A ponieważ większość języków jest zarządzana przez komisję, wątpię, byśmy mieli dużo woli politycznej, aby poprzeć taką zmianę.źródło
x < y < z
do(x < y) && (y < z)
. Wybierając gnidy, mentalnym modelem powiązanego porównania jest ogólna matematyka. Klasyczne porównanie nie jest ogólnie matematyką, ale logiką logiczną.x < y
daje odpowiedź binarną{0}
.y < z
daje odpowiedź binarną{1}
.{0} && {1}
daje opisową odpowiedź. Logika jest złożona, a nie naiwnie powiązana.x<y<z
. Język ma kiedyś szansę na uzyskanie czegoś takiego w odpowiedni sposób, a ta jedna szansa jest na początku jego istnienia.I have watched both Ruby and Python implement breaking changes.
Dla tych, którzy są ciekawi, oto przełomowa zmiana w C # 5.0 obejmująca zmienne i zamknięcia pętli: blogs.msdn.microsoft.com/ericlippert/2009/11/12/…Języki komputerowe próbują zdefiniować najmniejsze możliwe jednostki i pozwalają je łączyć. Najmniejszą możliwą jednostką byłoby coś w rodzaju „x <y”, co daje wynik logiczny.
Możesz poprosić o operatora trójskładnikowego. Przykładem może być x <y <z. Jakie kombinacje operatorów są dozwolone? Oczywiście x> y> z lub x> = y> = z lub x> y> = z, a może x == y == z powinno być dozwolone. Co z x <y> z? x! = y! = z? Co oznacza ostatni, x! = Y i y! = Z lub że wszystkie trzy są różne?
Teraz promocje argumentów: W C lub C ++ argumenty będą promowane do wspólnego typu. Co więc oznacza, że x <y <z oznacza x jest podwójne, ale y i z są długimi długimi int? Wszystkie trzy awansowały na podwojenie? Czy y jest brany podwojony raz, a tak długo długi drugi raz? Co się stanie, jeśli w C ++ jeden lub oba operatory zostaną przeciążone?
I na koniec, czy dopuszczasz dowolną liczbę operandów? Jak <b> c <d> e <f> g?
Cóż, wszystko się komplikuje. Teraz nie miałbym nic przeciwko, że x <y <z powoduje błąd składniowy. Ponieważ jego przydatność jest niewielka w porównaniu do szkód wyrządzonych początkującym, którzy nie mogą zrozumieć, co faktycznie robi x <y <z.
źródło
x + y + z
, z tą różnicą, że nie oznacza to żadnej różnicy semantycznej. Po prostu żaden dobrze znany język nigdy nie chciał tego zrobić.x < y < z
równoważna,((x < y) and (y < z))
ale zy
ocenianą tylko raz ), którą wyobrażam sobie, że skompilowane języki optymalizują się. „Ponieważ jego przydatność jest niewielka w porównaniu do szkód wyrządzonych początkującym, którzy nie mogą zrozumieć, co faktycznie robi x <y <z.” Myślę, że to niezwykle przydatne. Prawdopodobnie dostanie -1 za to ...a < b > c < d > e < f > g
, o „oczywistym” znaczeniu(a < b) and (b > c) and (c < d) and (d > e) and (e < f) and (f > g)
. To, że umiesz pisać, nie oznacza, że powinieneś. Eliminacja takich potworności należy do przeglądu kodu. Z drugiej strony pisanie0 < x < 8
w pythonie ma oczywiste (bez przestraszonych cytatów), co oznacza, że x leży między 0 a 8, wyłącznie.W wielu językach programowania
x < y
jest wyrażeniem binarnym, które akceptuje dwa operandy i zwraca jeden wynik boolowski. Dlatego, jeśli połączenie wielu wyrażeń będzie łańcuchowetrue < z
ifalse < z
nie będzie miało sensu, a jeśli wyrażenia te zostaną pomyślnie ocenione, prawdopodobnie wygenerują zły wynik.Jest dużo łatwiej myśleć
x < y
jako wywołanie funkcji , która przyjmuje dwa parametry i generuje pojedynczy wynik logiczną. W rzeczywistości tyle języków implementuje to pod maską. Jest łatwy do skompilowania i po prostu działa.x < y < z
Scenariusz jest znacznie bardziej skomplikowana. Teraz kompilator, w efekcie musi mody trzy funkcje:x < y
,y < z
, a wynik z tych dwóch wartości anded razem, wszystko w kontekście zapewne gramatyki języka dwuznaczny .Dlaczego zrobili to w drugą stronę? Ponieważ jest to jednoznaczna gramatyka, o wiele łatwiejsza do wdrożenia i o wiele łatwiejsza do poprawienia.
źródło
e1 op1 e2 op2 e3 op3 ...
jest równoważnee1 op e2 and e2 op2 e3 and ...
z tym że każdy wyraz jest oceniany tylko raz. (BTW, ta prosta reguła ma mylący efekt uboczny, że takie stwierdzeniaa == b is True
nie mają już zamierzonego efektu.)re:answer
tym mój umysł natychmiast poszedł do mojego komentarza na temat głównego pytania. Nie uważam wsparcia dlax < y < z
dodawania jakiejkolwiek konkretnej wartości do semantyki języka.(x < y) && (y < z)
jest szerzej wspierany, jest bardziej wyraźny, bardziej wyrazisty, łatwiejszy do przyswojenia w jego składowych, bardziej składalny, bardziej logiczny, łatwiejszy do refaktoryzacji.Większość języków głównego nurtu jest (przynajmniej częściowo) zorientowana obiektowo. Zasadniczo podstawową zasadą OO jest to, że obiekty wysyłają wiadomości do innych obiektów (lub do siebie), a odbiorca tej wiadomości ma pełną kontrolę nad sposobem odpowiedzi na tę wiadomość.
Zobaczmy teraz, jak moglibyśmy wprowadzić coś takiego
Możemy to ocenić ściśle od lewej do prawej (lewostronne):
Ale teraz wzywamy
__lt__
do wynikua.__lt__(b)
, który jestBoolean
. To nie ma sensu.Spróbujmy odpowiednio skojarzyć:
Nie, to też nie ma sensu. Teraz mamy
a < (something that's a Boolean)
.Okej, a co z traktowaniem go jak cukru syntaktycznego? Zróbmy łańcuch n
<
porównań i wyślij wiadomość n-1-ary. Może to oznaczać, możemy wysłać wiadomość__lt__
doa
, przechodzącb
ic
jako argumenty:Okej, to działa, ale tutaj jest dziwna asymetria:
a
decyduje, czy jest mniejsza niżb
. Aleb
nie ma się zdecydować, czy jest ona mniejsza niżc
, zamiast tego, że decyzja jest również wykonany przeza
.A co z interpretowaniem go jako wiadomości n-ary wysyłanej do
this
?Wreszcie! To może działać. Oznacza to jednak, że kolejność obiektów nie jest już własnością obiektu (np. Czy
a
jest mniejsza niżb
żadna z właściwościa
ani zb
), ale zamiast tego jest właściwością kontekstu (tjthis
.).Z głównego nurtu, który wydaje się dziwny. Jednak np. W Haskell jest to normalne. Na przykład może istnieć wiele różnych implementacji tej
Ord
klasy, a to, czy to, czy niea
jest mniejszab
, zależy od tego, która instancja klasy jest objęta zakresem.Ale tak naprawdę wcale nie jest to takie dziwne! Zarówno Java (
Comparator
), jak i .NET (IComparer
) mają interfejsy, które pozwalają wprowadzić własną relację porządkowania np. Do algorytmów sortowania. W ten sposób w pełni uznają, że uporządkowanie nie jest czymś, co jest ustalone na typ, ale zależy od kontekstu.O ile mi wiadomo, obecnie nie ma języków, które wykonują takie tłumaczenie. Istnieje jednak pierwszeństwo: zarówno Ioke, jak i Seph mają to, co ich projektant nazywa „operatorami trójkowymi” - operatorami składniowo dwójkowymi, ale semantycznymi trzeciorzędnymi. W szczególności,
nie jest interpretowane jako wysłanie wiadomości
=
doa
przekazaniab
jako argument, ale raczej jako wysłanie wiadomości=
do „aktualnego Ground” (koncepcja podobna, ale nie identycznathis
) przekazaniea
ib
jako argument. Więc,a = b
jest interpretowany jakoi nie
Można to łatwo uogólnić na operatory n-ary.
Zauważ, że jest to naprawdę specyficzne dla języków OO. W OO zawsze mamy jeden obiekt, który jest ostatecznie odpowiedzialny za interpretację wysłanej wiadomości, i jak widzieliśmy, nie jest to od razu oczywiste dla czegoś takiego jak
a < b < c
który obiekt powinien być.Nie dotyczy to jednak języków proceduralnych ani funkcjonalnych. Na przykład w Scheme , Common Lisp i Clojure The
<
funkcji n-ary i mogą być wywoływane z dowolnej liczby argumentów.W szczególności,
<
czy nie oznacza „mniej niż”, a funkcje te są interpretowane nieco inaczej:źródło
Po prostu dlatego, że projektanci języków o tym nie pomyśleli lub nie sądzili, że to dobry pomysł. Python robi to tak, jak to opisano za pomocą prostej (prawie) gramatyki LL (1).
źródło
1 < 2 < 3
do Java lub C # i nie masz problemu z pierwszeństwem operatora; masz problem z nieprawidłowymi typami. Problem polega na tym, że nadal będzie on analizowany dokładnie tak, jak go napisałeś, ale potrzebujesz logiki specjalnego przypadku w kompilatorze, aby przekształcić go z sekwencji pojedynczych porównań w porównanie łańcuchowe.Poniższy program C ++ kompiluje się z nieznanym peep od clang, nawet z ostrzeżeniami ustawionymi na najwyższy możliwy poziom (
-Weverything
):Z drugiej strony pakiet kompilatora GNU ładnie mnie ostrzega
comparisons like 'X<=Y<=Z' do not have their mathematical meaning [-Wparentheses]
.Odpowiedź jest prosta: Kompatybilność wsteczna. Na wolności istnieje ogromna ilość kodu, który używa odpowiednika
1<3<2
i oczekuje, że wynik będzie prawdziwy.Projektant językowy ma tylko jedną szansę, aby uzyskać to „właściwe”, i właśnie w tym momencie język został zaprojektowany. Zrozumienie tego na początku oznacza, że inni programiści raczej szybko skorzystają z tego „złego” zachowania. Poprawne ustawienie za drugim razem złamie istniejącą bazę kodu.
źródło
if x == y is True : ...
, moim zdaniem: ludzie, którzy piszą tego rodzaju kodeks, zasługują na poddanie się specjalnym, nadzwyczajnym rodzajom tortur, które (gdyby żył teraz) spowodowałyby, że sam Torquemada zemdleje.