Dlaczego więcej języków nie ma możliwości porównania wartości z więcej niż jedną inną wartością? [Zamknięte]


10

Rozważ następujące:

if(a == b or c)

W większości języków należy to zapisać jako:

if(a == b or a == c)

co jest nieco kłopotliwe i powtarza informacje.

Wiem, że moja powyższa przykładowa składnia jest nieco niezgrabna, ale jestem pewien, że są lepsze sposoby przekazania tego pomysłu.

Dlaczego nie oferuje tego więcej języków? Czy występują problemy z wydajnością lub składnią?


6
SQL oferuje, że: gdzie A IN (B, C)
czwartek

4
Nie pytałam o języki, które go oferują, lub mogą go mieć, ale dlaczego nie oferuje go więcej języków? Czy występują problemy z wydajnością lub składnią?
Zeroth

8
aby uogólnić odpowiedź @ Czwartek, w większości języków zwykle robisz to z ograniczeniem zbioru. (Lub listę lub krotkę, jeśli to łatwiejsze.) Działa to samo i pozwala uniknąć pewnych potencjalnie trudnych problemów ze składnią. W twoim przykładzie, czy „b lub c” oznacza zbiór „{b, c}”, czy też jest operatorem takim jak || ? W pythonie „b lub c” oznacza „wartość b, jeśli prawda, lub wartość c”
Rob

4
Zasadniczo jest to problem ze składnią. Problemem jest intuicyjny sposób rozróżnienia różnicy między „b lub c” i „b lub miałby c”.
YoungJohn

2
Jest to dość hackerskie w specjalnym przypadku a == b or ci nawet nie działa dobrze IMHO.

Odpowiedzi:


24

Problem ze składnią polega na tym, że wymaga ona składni.

Bez względu na to, jaką składnię ma Twój język, ludzie używający tego języka muszą się go nauczyć. W przeciwnym razie grozi im zobaczenie kodu i brak wiedzy o jego działaniu. Dlatego ogólnie uważa się za dobrą rzecz, jeśli język ma prostą składnię, która czysto obsługuje wiele przypadków.

W twoim konkretnym przykładzie próbujesz pobrać operator infix (funkcja, która przyjmuje dwa argumenty, ale jest zapisany Argument1 Operator Argument2) i próbujesz rozszerzyć go na wiele argumentów. To nie działa bardzo czysto, ponieważ cały punkt operatorów infix, o ile istnieje, polega na umieszczeniu operatora pomiędzy dwoma argumentami. Wydłużenie do (Argument1 Operator Argument2 MagicallyClearSymbol Argument3...)wydaje się nie dodawać dużo przejrzystości Equals(Arg1,Arg2,...). Infix jest zwykle używany do naśladowania konwencji matematycznych, które ludzie znają, co nie byłoby prawdą w przypadku alternatywnej składni.

Nie ma żadnych szczególnych problemów z wydajnością związanych z twoim pomysłem, poza tym, że parser musiałby poradzić sobie z gramatyką z inną regułą produkcyjną lub dwiema, co może mieć niewielki wpływ na szybkość parsowania. Może to mieć znaczenie dla języka interpretowanego lub skompilowanego w JIT, ale prawdopodobnie nie jest to duża różnica.

Większy problem z tym pomysłem polega na tym, że tworzenie wielu specjalnych przypadków w języku jest złym pomysłem .


1
Poza tym: Scala ma operatorów infix z dowolną liczbą argumentów, ponieważ operatory infix są tylko wywołaniami metod bez znaku .. Byliby zapisani jako arg1 op (arg2, arg3). Niezupełnie piękny, ale potrzebny w niektórych miejscach w kontekście tego języka.
amon

co if my_var in (a, b)wtedy? czy to nie jest więcej kwestia użycia odpowiedniego narzędzia do pracy?

Świetne punkty. Składnia języka powinna być podstawową częścią języka, a następnie budujesz na nim biblioteki. Jeśli język jest zbyt zagracony „pomocnym” cukrem syntaktycznym, staje się trudniejszy w użyciu. Nie wszyscy potrzebują, a == b or ca inni tego chcą a == b or c but not d. IMO właśnie tam na ratunek przychodzą funkcje / biblioteki narzędzi.
Allan

Być może potrzebny jest środek, za pomocą którego metoda może określić, że wywołanie z dowolną liczbą argumentów powinno być obsługiwane jako wiele wywołań, a wyniki w jakiś sposób połączone. Jeśli f().Equals(a,b,c); można ocenić, ponieważ (var temp=f(); temp.Equals(a)||temp.Equals(b)||temp.Equals(c))ta składnia byłaby idealna, ale jeśli int[] arr = {a,b,c}; f().Equals(arr);byłaby oceniona jako taka, nie byłoby to tak dobre, zwłaszcza gdyby dla każdego wywołania trzeba było utworzyć nową tablicę.
supercat

6

Ponieważ jest to bezproblemowe, a jego rozwiązanie przynosi zasadniczo zerową korzyść, ale wdrożenie go przynosi niezerowe koszty.

Istniejące funkcje oparte na zakresie i takie, które praktycznie każdy język oferuje, mogą doskonale działać w tej sytuacji, jeśli skalują się do rozmiaru, w którym a == b || a == cnie można ich wyciąć.


2
+1, ale myślę, że odpowiedź zostałaby poprawiona poprzez pokazanie jednej lub dwóch z tych „istniejących funkcji opartych na zakresie, które praktycznie każdy język [oferuje]”, tak więc ta alternatywa byłaby jaśniejsza.
Avner Shahar-Kashtan

Czy możesz udowodnić, że „przynosi on zasadniczo zerową korzyść, ale jego wdrożenie przynosi niezerowe koszty”?
Darek Nędza

3
@ DarekNędza Druga połowa nie powinna być kontrowersyjna: każda funkcja musi zostać przemyślana, wdrożona, przetestowana, udokumentowana i obsługiwana. Żaden z tych kroków nie jest bezpłatny przy żadnych rozsądnych miarach (czas ludzi, koszt alternatywny, złożoność, koszt pieniężny, jeśli ktoś jest opłacany za pracę nad nim itp.).

@ AvnerShahar-Kashtan zgodził się - dla mnie nie jest oczywiste, jak by to wyglądało, powiedzmy, java, sh, czy zsh? Ok, mógł sugerować „nowoczesny” język. Groovy?
Volker Siegel

W PHP to by wyglądało in_array($a, [$b, $c, $d, $e, $f]). : P
cHao

6

Niektóre języki mają takie funkcje. Np. W Perl6 możemy użyć połączeń , które są „superpozycjami” dwóch wartości:

if $a == any($b, $c) {
    say "yes";
}

# syntactic sugar for the above
if $a == $b | $c {
    say "yes";
}

Połączenia pozwalają nam wyrażać operacje na zbiorze danych dość zwięźle, podobnie do sposobu, w jaki operacje skalarne rozkładają się na kolekcje w niektórych językach. Np. Używając Pythona z numpy, porównanie można rozłożyć na wszystkie wartości:

import numpy as np
2 == np.array([1, 2, 3])
#=> np.array([False, True, False], dtype=np.bool)
(2 == np.array([1, 2, 3])).any()
#=> True

Działa to jednak tylko dla wybranych typów pierwotnych.

Dlaczego skrzyżowania są problematyczne? Ponieważ operacje na skrzyżowaniu rozkładają się na zawarte w nim wartości, sam obiekt skrzyżowania zachowuje się jak proxy dla wywołań metod - coś, co może obsłużyć kilka systemów typów oprócz typowania kaczego.

Problemów z układem typów można uniknąć, jeśli takie połączenia są dozwolone tylko jako specjalna składnia wokół operatorów porównania. Ale w tym przypadku są one tak ograniczone, że nie dodają wystarczającej wartości, aby dodać je do rozsądnego języka. To samo zachowanie można wyrazić za pomocą operacji ustawiania lub ręcznego wypisywania wszystkich porównań, a większość języków nie wierzy w dodanie zbędnej składni, jeśli istnieje już doskonale doskonałe rozwiązanie.


Ten konkretny przykład może być przepisany jaśniej jako 2 in [1, 2, 3]. Z drugiej strony, jeśli numpy ma .all()coś lub coś, równoważny zwykły python nie jest tak zwięzły.
Izkata

@Izkata W szczególności nie korzystałem z operacji ustawiania. Chociaż mój przykład używał ==operatora, możemy również użyć <zamiast tego - gdzie jest interaz twój ? Połączenia są bardziej ogólne niż ustawione testy członkostwa, ponieważ operacje na skrzyżowaniu rozkładają się na wszystkich członków - (x|y).foojest x.foo|y.foo, dopóki połączenie nie zostanie ostatecznie zwinięte do jednej wartości. Podany kod NumPy pokazuje dokładnie równoważne, ale bardziej pełne tłumaczenie połączeń Perl6, przy założeniu, że są to typy pierwotne.
amon

2

W językach z makrami łatwo jest dodać coś takiego, jeśli jeszcze go nie ma. Rozważ rakietę

(define-syntax-rule (equal-any? a b ...)
  (or (equal? a b) ...))
(equal-any? "a" "b" "a")
> #t

W innych językach bez metaprogramowania, być może możesz przeformułować to jako sprawdzanie członkostwa w ustawieniach / listach, być może:

if a ∈ {b, c}

2
Pierwsze dwa sprawdzają, czy wszystkie argumenty są równe; OP chce sprawdzić, czy pierwszy argument jest równy jednemu z poniższych. Co ciekawe, trzeci fragment, który pokazujesz, szanuje to.

@delnan Przepraszamy, źle zrozumiałem. Zredagowałem to.
Phil

2

W niektórych (popularnych) językach ==operator nie jest przechodni. Na przykład w JavaScript 0jest równy zarówno ''i '0', ale ''i wtedy '0'nie jest sobie równy. Więcej takich dziwactw w PHP.

Oznacza to, a == b == cże dodałoby to kolejną niejednoznaczność, ponieważ może dać inny wynik w zależności od tego, czy jest interpretowany jako (a == b) & (a == c)czy (a == b) & (a == c) & (b == c).


2

W większości języków powinno to być trywialnie osiągalne przez napisanie Infunkcji, więc dlaczego warto włączyć ją do rzeczywistego języka?

Na przykład Linq Contains().

Dobra, dla was wszystkich pedantów, oto moja implementacja w C #:

public static bool In<T>(this T obj, params T[] values)
{
    for(int i=0; i < values.Length; i++)
    {
        if (object.Equals(obj, values[i]))
            return true;
    }
    return false;
}

Działa na zakresie wartości w czasie wykonywania, a nie krotce, ponieważ kod OP można by wyrazić jako.
DeadMG

Wydaje się, że to, że jest łatwe, nie oznacza, że ​​nie należy tego robić. Jego ... rozważ budowę. Dlaczego zawsze musimy ręcznie pisać wszystkie te podstawowe funkcje i algorytmy w kółko?
Zeroth

5
@Zeroth może piszesz to samo w kółko, ale inni zamiast tego używają mechanizmów abstrakcji oferowanych przez ich język. Jeśli widzisz siebie pisząc a == b || a == cwiele razy, może nadszedł czasequals_any(a, {b, c})
Amon

Implementacja „zawiera” nie łatwo rozszerza się na takie rzeczy jak if (a > (b or c))i if (a mod (b or c) == 2).
tobyink

1
Czy ktoś powiedział pedantów? :) To pętle foreach, więc nie ma izmiennej. I ogólnie rzecz biorąc, wydaje się, że napisano to po długim dniu :) Ponieważ umieszczenie zarówno wewnątrz, jak return truei return falsewewnątrz pętli oznacza, że ​​nie ma mowy, aby kiedykolwiek wyszło poza pierwszą iterację. Porównujesz tylko z pierwszym value. Nawiasem mówiąc, dlaczego nie skorzystać z Anysugestii @Bob i uprościć jąreturn values.Any(value => Object.Equals(obj, value));
Konrad Morawski

1

„if (a == b lub c)” działa w większości języków: jeśli a == b lub jeśli c nie jest ujemne, zerowe lub zerowe.

Narzekanie, że jest to gadatliwe, nie ma sensu: nie powinieneś układać kilkunastu rzeczy w warunkowe. Jeśli chcesz porównać jedną wartość z dowolną liczbą innych wartości, zbuduj podprogram.


3
Które języki stanowią „większość”?
FrustratedWithFormsDesigner

1
@FrustratedWithFormsDesigner, cóż, jeśli coceniany jest na wartość logiczną, prawie każdy język może sobie z tym poradzić a == b || c:)
Brian S

@BrianS: Zakładałem, że OP oznacza dosłowną składnię if(a == b or c). Myślę, że muszę zrobić sobie przerwę ...: P
FrustratedWithFormsDesigner

@FrustratedWithFormsDesigner Lisp! ... co? ... :)
Volker Siegel

3
To naprawdę nie ma sensu pytanie. if (a == b or c)jest pseudo-kodem, aby sprawdzić, czy ajest równy blub arówny c. Nie ma na celu sprawdzenia, cczy nie jest to zero.
hvd

1

Zwykle chcesz ograniczyć składnię do minimum i zamiast tego pozwolić, aby takie konstrukcje były definiowane w samym języku.

Na przykład w Haskell można przekonwertować dowolną funkcję z dwoma lub więcej argumentami na operator infix za pomocą odwrotnych poleceń. To pozwala napisać:

if a `elem` [b, c] then ... else ...

gdzie elemjest po prostu normalna funkcja pobierająca dwa argumenty - wartość i listę wartości - i sprawdza, czy pierwszy jest elementem drugiego.

Co jeśli chcesz użyć andzamiast or? W Haskell możesz po prostu użyć następujących poleceń zamiast czekać, aż dostawca kompilatora zaimplementuje nową funkcję:

 if all (== a) [b, c] then ... else ...

1
Dlaczego ktoś chciałby ograniczyć składnię do minimum? Co dokładnie tam się dzieje? Nie wygłaszaj takich oświadczeń bez poparcia argumentów. ;)
Zeroth

1

Niektóre języki to oferują - do pewnego stopnia.

Może nie jako twój konkretny przykład, ale weźmy na przykład wiersz Python:

def minmax(min, max):
    def answer(value):
        return max > value > min
    return answer

inbounds = minmax(5, 15)
inbounds(7) ##returns True
inbounds(3) ##returns False
inbounds(18) ##returns False

Tak więc niektóre języki są w porządku z wieloma porównaniami, pod warunkiem, że wyrażasz to poprawnie.

Niestety nie działa tak, jak można by się tego spodziewać w przypadku porównań.

>>> def foo(a, b):
...     def answer(value):
...         return value == a or b
...     return answer
... 
>>> tester = foo(2, 4)
>>> tester(3)
4
>>> tester(2)
True
>>> tester(4)
4
>>> 

„Co masz na myśli mówiąc, że zwraca True lub 4?” - zatrudnienie po tobie

Jednym rozwiązaniem w tym przypadku, przynajmniej w Pythonie, jest użycie go nieco inaczej:

>>> def bar(a, b):
...     def ans(val):
...             return val == a or val == b
...     return ans
... 
>>> this = bar(4, 10)
>>> this(5)
False
>>> this(4)
True
>>> this(10)
True
>>> this(9)
False
>>> 

EDYCJA: Poniższe czynności również zrobiłyby coś podobnego, ponownie w Pythonie ...

>>> def bar(a, b):
...     def answer(val):
...             return val in (a, b)
...     return answer
... 
>>> this = bar(3, 5)
>>> this(3)
True
>>> this(4)
False
>>> this(5)
True
>>> 

Niezależnie od tego, jakiego używasz języka, może nie być tak, że nie możesz tego zrobić, po prostu musisz najpierw przyjrzeć się, jak działa logika. Zazwyczaj jest to kwestia znajomości tego, o co „pytasz” język, który ma ci powiedzieć.


1

Metoda indexOf stosowana w macierzy, którą posiadają wszystkie języki, pozwala porównać wartość z kilkoma innymi, więc chyba specjalny operator nie ma większego sensu.

W javascript, który napisałby:

if ( [b, c].indexOf(a) != -1 ) { ....  }

0

Pytasz, dlaczego nie możemy tego zrobić: if(a == b or c)

Python robi to bardzo skutecznie, w rzeczywistości najskuteczniej z set:

if a in set([b, c]):
    then_do_this()

W przypadku testowania członkostwa „ustaw” sprawdza, czy skróty elementu są takie same, i dopiero wtedy porównuje się pod kątem równości, więc elementy b i c muszą być mieszalne, w przeciwnym razie lista bezpośrednio porównuje równość:

if a in [b, c]:
    then_do_this()

0

Języki w stylu APL pozwalają porównać skalar z każdym elementem w wektorze w jednej operacji. To tworzy wektor boolowski. Na przykład chciałbym bezwstydnie promować mój minimalnie funkcjonalny kalkulator apl, inca ( tłumacz online ).

   a<5
5 
   b<4
4 
   c<5
5 
   a=b c
0 1 

Aby sprowadzić to do jednej wartości, możemy wykonać włącznie lub sumując i sprawdzając wartość niezerową.

   0!+/a=b c
1 
   c<6
6 
   0!+/a=b c
0

Tak więc, jak mówią inne odpowiedzi, problemem jest składnia. Do pewnego stopnia składniowe rozwiązania nie stwierdzono, być może w dużym kosztem nauki paradygmat tablicy.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.