Czy istnieje konkretny powód słabej czytelności projektowania składni wyrażeń regularnych?

160

Wydaje się, że wszyscy programiści zgadzają się, że czytelność kodu jest znacznie ważniejsza niż jednokreskowe skrócone składnie, które działają, ale wymagają od starszego programisty interpretacji z dowolnym stopniem dokładności - ale wydaje się, że dokładnie tak zaprojektowano wyrażenia regularne. Czy był tego powód?

Wszyscy zgadzamy się, że selfDocumentingMethodName()jest o wiele lepszy niż e(). Dlaczego to nie powinno dotyczyć również wyrażeń regularnych?

Wydaje mi się, że zamiast projektować składnię logiki jednowierszowej bez organizacji strukturalnej:

var parse_url = /^(?:([A-Za-z]+):)?(\/{0,3})(0-9.\-A-Za-z]+)(?::(\d+))?(?:\/([^?#]*))?(?:\?([^#]*))?(?:#(.*))?$/;

I to nie jest nawet ścisłe parsowanie adresu URL!

Zamiast tego możemy sprawić, by struktura potoku była zorganizowana i czytelna, na przykład:

string.regex
   .isRange('A-Z' || 'a-z')
   .followedBy('/r');

Jaką przewagę oferuje niezwykle zwięzła składnia wyrażenia regularnego, inna niż najkrótsza możliwa operacja i składnia logiczna? Czy istnieje konkretny techniczny powód słabej czytelności projektowania składni wyrażeń regularnych?

Viziionary
źródło
Komentarze nie są przeznaczone do rozszerzonej dyskusji; ta rozmowa została przeniesiona do czatu .
wałek klonowy
1
Próbowałem rozwiązać ten problem z czytelnością za pomocą biblioteki o nazwie RegexToolbox. Do tej pory jest przenoszony do C #, Java i JavaScript - patrz github.com/markwhitaker/RegexToolbox.CSharp .
Mark Whitaker,
podjęto wiele prób rozwiązania tego problemu, ale kultura jest trudna do zmiany. zobacz moją odpowiedź na temat wyrażeń werbalnych tutaj . Ludzie sięgają po najniższe dostępne narzędzie.
Parivar Saraff

Odpowiedzi:

178

Jest jeden wielki powód, dla którego wyrażenia regularne zostały zaprojektowane tak zwięźle, jak są: zostały zaprojektowane do użycia jako polecenia edytora kodu, a nie jako język do kodowania. Dokładniej, edbył jednym z pierwszych programów, który używał wyrażeń regularnych i stamtąd wyrażenia regularne rozpoczęły swój podbój o dominację nad światem. Na przykład edpolecenie g/<regular expression>/pwkrótce zainspirowało osobny program o nazwie grep, który jest nadal używany. Ze względu na swoją moc zostały następnie znormalizowane i wykorzystane w różnych narzędziach, takich jak sedivim

Ale wystarczy na ciekawostki. Dlaczego więc to pochodzenie faworyzuje zwięzłą gramatykę? Ponieważ nie wpisujesz polecenia edytora, aby przeczytać go jeszcze raz. Wystarczy, że pamiętasz, jak to złożyć, i że możesz robić to, co chcesz. Jednak każdy znak, który musisz wpisać, spowalnia postęp edycji pliku. Składnia wyrażeń regularnych została zaprojektowana do zapisywania stosunkowo skomplikowanych wyszukiwań w sposób odrzutowy, i właśnie to sprawia, że ​​ludzie mają problemy z głowami, którzy używają ich jako kodu do analizowania niektórych danych wejściowych do programu.

cmaster
źródło
5
Wyrażenia regularne nie są przeznaczone do analizy. inaczej, stackoverflow.com/questions/1732348/... . i bóle głowy.
njzk2
19
@ njzk2 Ta odpowiedź jest w rzeczywistości błędna. HTML dokument nie jest język regularny, ale HTML otwarty znacznik , który jest co pytanie pyta o, faktycznie jest.
Random832
11
To dobra odpowiedź wyjaśniająca, dlaczego pierwotne wyrażenie regularne jest tak tajemnicze, jak to jest, ale nie wyjaśnia, dlaczego obecnie nie ma alternatywnego standardu o zwiększonej czytelności.
Doc Brown
13
Więc dla tych, którzy myślą, że grepjest to źle wymówione „chwycić”, w rzeczywistości pochodzi od g/ re(dla wyrażenia regularnego) / p?
Hagen von Eitzen,
6
@DannyPflughoeft Nie, nie ma. Otwarty tag jest po prostu <aaa bbb="ccc" ddd='eee'>, nie ma w nim zagnieżdżonych tagów. Nie można zagnieżdżać znaczników, to, co jest zagnieżdżane, to elementy (otwarty znacznik, zawartość zawierająca elementy potomne, zamknięty znacznik), o które nie pytano o parsowanie. Tagi HTML są zwykłym językiem - równoważenie / zagnieżdżanie zachodzi na poziomie powyżej znaczników.
Random832
62

Przytoczone przez ciebie wyrażenie regularne jest strasznym bałaganem i nie sądzę, żeby ktokolwiek zgodził się z tym, że można je odczytać. Jednocześnie duża część tej brzydoty wiąże się z rozwiązaniem problemu: istnieje kilka warstw zagnieżdżania, a gramatyka adresów URL jest stosunkowo skomplikowana (z pewnością zbyt skomplikowana, aby komunikować się zwięźle w dowolnym języku). Jednak z pewnością jest prawdą, że istnieją lepsze sposoby na opisanie tego wyrażenia regularnego. Dlaczego więc nie są używane?

Głównym powodem jest bezwładność i wszechobecność. To nie wyjaśnia, dlaczego stały się tak popularne, ale teraz, gdy już są, każdy, kto zna wyrażenia regularne, może korzystać z tych umiejętności (z niewielkimi różnicami między dialektami) w stu różnych językach i dodatkowym tysiącem narzędzi programowych ( np. edytory tekstu i narzędzia wiersza poleceń). Nawiasem mówiąc, te ostatnie nie mogłyby i nie mogłyby zastosować żadnego rozwiązania, które sprowadzałoby się do pisania programów , ponieważ są one intensywnie używane przez nie-programistów.

Mimo to wyrażenia regularne są często nadużywane, to znaczy stosowane nawet wtedy, gdy inne narzędzie byłoby znacznie lepsze. Nie sądzę, żeby składnia wyrażeń regularnych była okropna . Ale jest wyraźnie lepszy w krótkich i prostych wzorach: archetypowy przykład identyfikatorów w językach podobnych do C [a-zA-Z_][a-zA-Z0-9_]*można odczytać z absolutnym minimum znajomości wyrażeń regularnych, a kiedy ten pasek zostanie spełniony, jest on zarówno oczywisty, jak i bardzo zwięzły. Wymaganie mniejszej liczby znaków nie jest z natury złe, wręcz przeciwnie. Bycie zwięzłym to zaleta, pod warunkiem, że będziesz zrozumiały.

Są co najmniej dwa powody, dla których ta składnia wyróżnia się prostymi wzorami takimi jak te: Nie wymaga zmiany znaczenia dla większości znaków, więc czyta się stosunkowo naturalnie i używa wszystkich dostępnych interpunkcji, aby wyrazić różnorodne proste kombinatory parsowania. Być może, co najważniejsze, nie wymaga niczego do sekwencjonowania. Piszesz pierwszą rzecz, a potem następną. Porównaj to z twoim followedBy, szczególnie gdy poniższy wzór nie jest dosłownym, ale bardziej skomplikowanym wyrażeniem.

Dlaczego więc nie udaje im się w bardziej skomplikowanych przypadkach? Widzę trzy główne problemy:

  1. Brak możliwości abstrakcji. Gramatyki formalne, które wywodzą się z tej samej dziedziny informatyki teoretycznej co wyrażenia regularne, mają zestaw produkcji, dzięki czemu mogą nadawać nazwy pośrednim częściom wzoru:

    # This is not equivalent to the regex in the question
    # It's just a mock-up of what a grammar could look like
    url      ::= protocol? '/'? '/'? '/'? (domain_part '.')+ tld
    protocol ::= letter+ ':'
    ...
    
  2. Jak widzieliśmy powyżej, białe znaki nie mające specjalnego znaczenia są przydatne, aby umożliwić formatowanie, które jest łatwiejsze dla oczu. To samo z komentarzami. Wyrażenia regularne nie mogą tego zrobić, ponieważ spacja jest po prostu literałem ' '. Uwaga: niektóre implementacje pozwalają na tryb „pełny”, w którym białe znaki są ignorowane i możliwe są komentarze.

  3. Nie ma metajęzyka opisującego typowe wzorce i kombinatory. Na przykład, można napisać digitregułę raz i używać jej w gramatyce bezkontekstowej, ale nie można zdefiniować „funkcji”, że tak powiem, która ma produkcję pi tworzy nową produkcję, która robi z nią coś dodatkowego, na przykład tworzy produkcja rozdzielonej przecinkami listy wystąpień p.

Proponowane przez ciebie podejście z pewnością rozwiązuje te problemy. Po prostu nie rozwiązuje ich zbyt dobrze, ponieważ zamienia się w o wiele bardziej zwięzłe, niż to konieczne. Pierwsze dwa problemy można rozwiązać, pozostając w stosunkowo prostym i zwięzłym języku specyficznym dla domeny. Trzecie, cóż ... programowe rozwiązanie wymaga oczywiście ogólnego języka programowania, ale z mojego doświadczenia wynika, że ​​trzeci jest zdecydowanie najmniejszym z tych problemów. Niewiele wzorów ma wystarczającą liczbę wystąpień tego samego złożonego zadania, które programiści tęsknią za umiejętnością definiowania nowych kombinacji. A gdy jest to konieczne, język jest często na tyle skomplikowany, że nie może i nie powinien być analizowany z wyrażeniami regularnymi.

Istnieją rozwiązania dla tych przypadków. Istnieje około dziesięciu tysięcy bibliotek kombinatora parserów, które wykonują mniej więcej to, co proponujesz, tylko z innym zestawem operacji, często inną składnią i prawie zawsze z większą mocą analizy niż wyrażenia regularne (tj. Zajmują się językami bezkontekstowymi lub dużymi rozmiarami podzbiór tych). Są też generatory parsera, które działają zgodnie z opisanym powyżej podejściem „użyj lepszego DSL”. I zawsze istnieje możliwość ręcznego zapisania części parsowania we właściwym kodzie. Możesz nawet mieszać i dopasowywać, używając wyrażeń regularnych do prostych zadań podrzędnych i wykonując skomplikowane czynności w kodzie, wywołując wyrażenia regularne.

Nie wiem wystarczająco dużo o pierwszych latach komputerów, aby wyjaśnić, dlaczego wyrażenia regularne stały się tak popularne. Ale są tutaj, by zostać. Musisz tylko mądrze z nich korzystać i nie używać ich, gdy jest to mądrzejsze.

Tulains Córdova
źródło
9
I don't know enough about the early years of computing to explain how regular expressions came to be so popular.Możemy jednak zaryzykować zgadywanie: podstawowy silnik wyrażeń regularnych jest bardzo łatwy do wdrożenia, o wiele łatwiejszy niż wydajny analizator kontekstowy.
biziclop
15
@biziclop Nie przeceniałbym tej zmiennej. Yacc, który najwyraźniej miał wystarczająco dużo poprzedników, aby nazwać go „ kolejnym kompilatorem kompilatorów”, został stworzony na początku lat 70. i był wcześniej dołączany do Uniksa grep(wersja 3 vs wersja 4). Wydaje się, że pierwsze duże użycie wyrażenia regularnego miało miejsce w 1968 r.
Mogę tylko przejść do tego, co znalazłem na Wikipedii (więc nie wierzę w 100%), ale zgodnie z tym, yaccpowstał w 1975 roku, cała idea parserów LALR (które były jedną z pierwszych klas praktycznie użytecznych parserów ich) kind) powstała w 1973 r. Podczas gdy pierwsza implementacja silnika wyrażeń regularnych, którą skompilowały wyrażenia JIT (!), została opublikowana w 1968 r. Ale masz rację, trudno powiedzieć, co ją zmieniło, w rzeczywistości trudno powiedzieć, kiedy wyrażenia regularne zaczęły „brać” poza". Podejrzewam jednak, że po umieszczeniu ich w edytorach tekstu, z których korzystali programiści, chcieli używać ich także we własnym oprogramowaniu.
biziclop
1
@ jpmc26 otwórz swoją książkę JavaScript The Good Parts to the Regex Chapter.
Viziionary
2
with very few differences between dialectsNie powiedziałbym, że to „bardzo mało”. Każda predefiniowana klasa znaków ma kilka definicji między różnymi dialektami. Są też dziwactwa podczas analizowania specyficzne dla każdego dialektu.
nhahtdh
39

Perspektywa historyczna

Artykuł w Wikipedii jest dość szczegółowy na temat pochodzenia wyrażeń regularnych (Kleene, 1956). Oryginalny składnia była stosunkowo prosta tylko *, +, ?, |i grupowania (...). To było zwięzłe ( i czytelne, oba niekoniecznie są przeciwne), ponieważ języki formalne są zwykle wyrażane zwięzłymi notacjami matematycznymi.

Później składnia i możliwości ewoluowały wraz z edytorami i rozwijały się wraz z Perlem , który starał się być zwięzły z założenia ( „typowe konstrukcje powinny być krótkie” ). To bardzo skomplikowało składnię, ale zauważ, że ludzie są teraz przyzwyczajeni do wyrażeń regularnych i potrafią je pisać (jeśli nie czytają). Fakt, że czasami są tylko do zapisu, sugeruje, że gdy są zbyt długie, zazwyczaj nie są odpowiednim narzędziem. Wyrażenia regularne są zwykle nieczytelne, gdy są nadużywane.

Poza ciągami wyrażeń regularnych

Mówiąc o alternatywnych składniach, spójrzmy na taką, która już istnieje ( cl-ppcre , w Common Lisp ). Długie wyrażenie regularne można przeanalizować ppcre:parse-stringw następujący sposób:

(let ((*print-case* :downcase)
      (*print-right-margin* 50))
  (pprint
   (ppcre:parse-string "^(?:([A-Za-z]+):)?(\\/{0,3})(0-9.\\-A-Za-z]+)(?::(\\d+))?(?:\\/([^?#]*))?(?:\\?([^#]*))?(?:#(.*))?$")))

... i daje następującą formę:

(:sequence :start-anchor
 (:greedy-repetition 0 1
  (:group
   (:sequence
    (:register
     (:greedy-repetition 1 nil
      (:char-class (:range #\A #\Z)
       (:range #\a #\z))))
    #\:)))
 (:register (:greedy-repetition 0 3 #\/))
 (:register
  (:sequence "0-9" :everything "-A-Za-z"
   (:greedy-repetition 1 nil #\])))
 (:greedy-repetition 0 1
  (:group
   (:sequence #\:
    (:register
     (:greedy-repetition 1 nil :digit-class)))))
 (:greedy-repetition 0 1
  (:group
   (:sequence #\/
    (:register
     (:greedy-repetition 0 nil
      (:inverted-char-class #\? #\#))))))
 (:greedy-repetition 0 1
  (:group
   (:sequence #\?
    (:register
     (:greedy-repetition 0 nil
      (:inverted-char-class #\#))))))
 (:greedy-repetition 0 1
  (:group
   (:sequence #\#
    (:register
     (:greedy-repetition 0 nil :everything)))))
 :end-anchor)

Ta składnia jest bardziej szczegółowa, a jeśli spojrzysz na komentarze poniżej, niekoniecznie bardziej czytelna. Nie zakładaj więc, że ponieważ masz mniej zwartą składnię, wszystko będzie automatycznie wyraźniejsze .

Jeśli jednak zaczniesz mieć problemy z wyrażeniami regularnymi, przekształcenie ich w ten format może pomóc w odszyfrowaniu i debugowaniu kodu. Jest to jedna zaleta w stosunku do formatów łańcuchowych, w których błąd pojedynczego znaku może być trudny do wykrycia. Główną zaletą tej składni jest manipulowanie wyrażeniami regularnymi przy użyciu formatu strukturalnego zamiast kodowania łańcuchowego. To pozwala ci komponować i budować takie wyrażenia, jak każda inna struktura danych w twoim programie. Kiedy używam powyższej składni, dzieje się tak zwykle dlatego, że chcę budować wyrażenia z mniejszych części (patrz także moja odpowiedź CodeGolf ). Na przykład możemy napisać 1 :

`(:sequence
   :start-anchor
   ,(protocol)
   ,(slashes)
   ,(domain)
   ,(top-level-domain) ... )

Wyrażenia regularne oparte na łańcuchach znaków można również komponować, używając konkatenacji łańcuchów i / lub interpolacji w funkcjach pomocniczych. Jednakże istnieją ograniczenia w ciąg manipulacji, które mają tendencję do zaśmiecać ten kod (myślę o gniazdujących problemów, podobnie backticks vs $(...)bash; także, ucieczka znaków może dać głowy).

Zauważ również, że powyższy formularz pozwala na (:regex "string")formularze, dzięki czemu możesz mieszać krótkie zapisy z drzewami. Wszystko to prowadzi IMHO do dobrej czytelności i kompozycyjności; rozwiązuje trzy problemy wyrażone przez delnan , pośrednio (tj. nie w języku samych wyrażeń regularnych).

Podsumowując

  • W większości przypadków notacja zwięzła jest w rzeczywistości czytelna. Istnieją trudności w radzeniu sobie z rozszerzonymi notacjami, które obejmują cofanie się itp., Ale ich użycie jest rzadko uzasadnione. Nieuzasadnione użycie wyrażeń regularnych może prowadzić do nieczytelnych wyrażeń.

  • Wyrażenia regularne nie muszą być kodowane jako ciągi znaków. Jeśli masz bibliotekę lub narzędzie, które pomoże Ci budować i komponować wyrażenia regularne, unikniesz wielu potencjalnych błędów związanych z manipulacjami ciągami.

  • Alternatywnie, gramatyki formalne są bardziej czytelne i lepiej nadają nazwy i abstrakcje podwyrażeniom. Terminale są ogólnie wyrażane jako proste wyrażenia regularne.


1. Wolisz budować swoje wyrażenia w czasie odczytu, ponieważ wyrażenia regularne zwykle są stałymi w aplikacji. Zobacz create-scanneri load-time-value:

'(:sequence :start-anchor #.(protocol) #.(slashes) ... )
rdzeń rdzeniowy
źródło
5
Może po prostu przyzwyczaiłem się do tradycyjnej składni RegEx, ale nie jestem pewien, czy 22 nieco czytelne linie są łatwiejsze do zrozumienia niż odpowiednik wyrażenia regularnego w jednym wierszu.
3
@ dan1111 „nieco czytelny” ;-) Ok, ale jeśli chcesz mieć naprawdę długie regex, ma sens, aby zdefiniować podzbiory, jak digits, identi komponowania ich. Sądzę, że to, co robię, polega na manipulacji ciągami (konkatenacji lub interpolacji), co powoduje inne problemy, takie jak właściwe ucieczkę. Wyszukaj na przykład wystąpienia \\\\`pakietów emacs. Btw, to jest gorzej, ponieważ sam znak escape jest używany zarówno dla znaków specjalnych, jak \ni \"i dla składni regex \(. Przykładem dobrej składni printf, %dktóry nie jest lisp , jest sytuacja, w której nie powoduje konfliktu \d.
coredump
1
słuszna uwaga na temat zdefiniowanych podzbiorów. To ma sens. Jestem tylko sceptyczny, że gadatliwość jest poprawą. Może to być łatwiejsze dla początkujących (chociaż takie koncepcje greedy-repetitionnie są intuicyjne i wciąż trzeba się ich nauczyć). Jednak poświęca użyteczność dla ekspertów, ponieważ znacznie trudniej jest dostrzec i uchwycić cały wzór.
@ dan1111 Zgadzam się, że sama gadatliwość nie jest poprawą. Poprawą może być manipulowanie wyrażeniem regularnym przy użyciu danych strukturalnych zamiast ciągów.
coredump
@ dan1111 Może powinienem zaproponować edycję za pomocą Haskell? Parsec robi to tylko w dziewięciu liniach; jako jedną wkładką: do {optional (many1 (letter) >> char ':'); choice (map string ["///","//","/",""]); many1 (oneOf "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-."); optional (char ':' >> many1 digit); optional (char '/' >> many (noneOf "?#")); optional (char '?' >> many (noneOf "#")); optional (char '#' >> many (noneOf "\n")); eof}. Z kilkoma liniami, takimi jak oznaczenie długiego sznurka, domainChars = ...i section start p = optional (char start >> many p)wygląda dość prosto.
CR Drost
25

Największym problemem związanym z regexem nie jest zbyt zwięzła składnia, lecz to, że próbujemy wyrazić złożoną definicję w jednym wyrażeniu, zamiast tworzyć ją z mniejszych elementów składowych. Jest to podobne do programowania, w którym nigdy nie używasz zmiennych i funkcji, a zamiast tego osadzasz kod w jednym wierszu.

Porównaj wyrażenie regularne z BNF . Jego składnia nie jest o wiele bardziej przejrzysta niż regex, ale jest używana inaczej. Zaczynasz od zdefiniowania prostych nazwanych symboli i komponujesz je, aż dojdziesz do symbolu opisującego cały wzór, który chcesz dopasować.

Na przykład spójrz na składnię URI w rfc3986 :

URI           = scheme ":" hier-part [ "?" query ] [ "#" fragment ]
scheme        = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
hier-part     = "//" authority path-abempty
              / path-absolute
              / path-rootless
              / path-empty
...

Można napisać prawie to samo przy użyciu wariantu składni wyrażenia regularnego, który obsługuje osadzanie nazwanych podwyrażeń.


Osobiście uważam, że krótka składnia wyrażenia regularnego jest odpowiednia dla często używanych funkcji, takich jak klasy postaci, konkatenacja, wybór lub powtarzanie, ale dla bardziej złożonych i rzadszych funkcji, takich jak pełne wypowiadanie nazw, są preferowane. Całkiem podobne do tego, w jaki sposób używamy operatorów takich jak +lub *w normalnym programowaniu i przełączamy się do nazwanych funkcji dla rzadszych operacji.

CodesInChaos
źródło
12

selfDocumentingMethodName () jest znacznie lepszy niż e ()

czy to jest Jest powód, dla którego większość języków ma {i} jako separatory bloków zamiast BEGIN i END.

Ludzie lubią zwięzłość, a kiedy znasz składnię, krótka terminologia jest lepsza. Wyobraź sobie swój przykład wyrażenia regularnego, jeśli d (dla cyfry) było „cyfrą”, wyrażenie byłoby bardziej przerażające do przeczytania. Jeśli uczynisz go łatwiejszym do analizy ze znakami kontrolnymi, będzie wyglądał bardziej jak XML. Żadne z nich nie są tak dobre, kiedy znasz składnię.

Aby właściwie odpowiedzieć na twoje pytanie, musisz zdać sobie sprawę, że wyrażenie regularne pochodzi z czasów, gdy zwięzłość była obowiązkowa. Łatwo jest sądzić, że dokument XML o wielkości 1 MB nie jest dzisiaj wielkim problemem, ale mówimy o dniach, w których 1 MB cała pojemność. Było wtedy mniej języków, a regex nie jest oddalony o milion mil od Perla lub C, więc składnia byłaby znana programistom, którzy byliby zadowoleni z nauki składni. Nie było więc powodu, aby uczynić to bardziej gadatliwym.

gbjbaanb
źródło
1
selfDocumentingMethodNamejest ogólnie uznawany za lepszy niż edlatego, że intuicja programisty nie zgadza się z rzeczywistością pod względem tego, co faktycznie stanowi czytelność lub kod dobrej jakości . Ludzie zgadzający się są w błędzie, ale tak właśnie jest.
Leushenko
1
@Leushenko: Czy twierdzisz, że e()jest to lepsze niż selfDocumentingMethodName()?
JacquesB
3
@JacquesB może nie we wszystkich kontekstach (jak nazwa globalna). Ale dla ciasnych przedmiotów? Prawie na pewno. Zdecydowanie częściej niż mówi konwencjonalna mądrość.
Leushenko
1
@Leushenko: Trudno mi wyobrazić sobie kontekst, w którym pojedyncza litera nazwa funkcji jest lepsza niż nazwa bardziej opisowa. Ale myślę, że to czysta opinia.
JacquesB
1
@MilesRout: Przykład dotyczy nazwy metody samok e()dokumentowania . Czy możesz wyjaśnić, w jakim kontekście lepiej jest używać jednoliterowych nazw metod zamiast opisowych nazw metod?
JacquesB
6

Regex jest jak klocki Lego. Na pierwszy rzut oka widać niektóre elementy plastikowe o różnych kształtach, które można połączyć. Możesz pomyśleć, że nie będzie zbyt wielu możliwych różnych rzeczy, które możesz ukształtować, ale potem zobaczysz niesamowite rzeczy, które robią inni ludzie i po prostu zastanawiasz się, jak niesamowita jest to zabawka.

Regex jest jak klocki Lego. Istnieje kilka argumentów, które można zastosować, ale łączenie ich w różne formy tworzy miliony różnych wzorców wyrażeń regularnych, które można wykorzystać do wielu skomplikowanych zadań.

Ludzie rzadko używali samych parametrów wyrażenia regularnego. Wiele języków oferuje funkcje sprawdzania długości łańcucha lub dzielenia z niego części liczbowych. Możesz używać funkcji łańcuchowych do krojenia i przekształcania tekstu. Siła wyrażenia regularnego jest zauważana, gdy używasz złożonych formularzy do wykonywania bardzo specyficznych złożonych zadań.

Możesz znaleźć dziesiątki tysięcy wyrażeń regularnych na SO i rzadko są one oznaczone jako duplikaty. To samo pokazuje możliwe unikalne przypadki użycia, które bardzo się od siebie różnią.

Nie jest łatwo zaoferować predefiniowane metody radzenia sobie z tak wieloma różnymi unikalnymi zadaniami. Masz funkcje łańcuchowe dla tego rodzaju zadań, ale jeśli te funkcje nie są wystarczające do zadania określonego, czas użyć wyrażenia regularnego

Upadły anioł
źródło
2

Rozumiem, że jest to raczej problem praktyki niż potencji. Problem zwykle pojawia się, gdy wyrażenia regularne są implementowane bezpośrednio , zamiast zakładać złożony charakter. Podobnie dobry programista rozkłada funkcje swojego programu na zwięzłe metody.

Na przykład ciąg wyrażenia regularnego dla adresu URL można zmniejszyć z około:

UriRe = [scheme][hier-part][query][fragment]

do:

UriRe = UriSchemeRe + UriHierRe + "(/?|/" + UriQueryRe + UriFragRe + ")"
UriSchemeRe = [scheme]
UriHierRe = [hier-part]
UriQueryRe = [query]
UriFragRe = [fragment]

Wyrażenia regularne to sprytne rzeczy, ale są podatne na nadużycia przez tych, którzy pochłonięci są swoją pozorną złożonością. Otrzymane wyrażenia są retoryczne, nie mają wartości długoterminowej.

toplel32
źródło
2
Niestety większość języków programowania nie zawiera funkcji ułatwiających komponowanie wyrażeń regularnych, a sposób przechwytywania grup nie jest zbyt przyjazny do komponowania.
CodesInChaos
1
Inne języki muszą nadrobić zaległości do Perla 5 w zakresie obsługi „wyrażeń regularnych zgodnych z Perlem”. Podwyrażenia to nie to samo, co zwykłe łączenie ciągów specyfikacji wyrażenia regularnego. Przechwytywanie powinno się nazywać, nie polegając na niejawnej numeracji.
JDługosz
0

Jak mówi @cmaster, wyrażenia regularne zostały pierwotnie zaprojektowane do użycia tylko w locie, i po prostu dziwne (i nieco przygnębiające) jest to, że składnia szumu linii jest nadal najbardziej popularna. Jedyne wyjaśnienia, jakie mogę wymyślić, obejmują bezwładność, masochizm lub machismo (nierzadko „bezwładność” jest najbardziej atrakcyjnym powodem do zrobienia czegoś ...)

Perl podejmuje raczej słabą próbę uczynienia ich bardziej czytelnymi, dopuszczając białe spacje i komentarze, ale nie robi nic z wyobraźnią.

Istnieją inne składnie. Dobrym przykładem jest składnia scsh dla wyrażeń regularnych , które z mojego doświadczenia tworzą wyrażenia regularne , które są dość łatwe do napisania, ale mimo to są czytelne.

[ scsh jest wspaniały z innych powodów, z których jednym jest słynny tekst z podziękowaniami ]

Norman Gray
źródło
2
Perl6 robi! Spójrz na gramatyki.
JDługosz
@ JDługosz O ile mi wiadomo, to bardziej przypomina mechanizm generatorów analizatorów składni, niż alternatywną składnię wyrażeń regularnych. Ale rozróżnienie może nie jest głębokie.
Norman Gray
Może być zamiennikiem, ale nie ogranicza się do tej samej mocy. Możesz przetłumaczyć regedp na gramatykę liniową z 1 do 1 korespondencją modyfikatorów, ale w bardziej czytelnej składni. Przykłady promujące go jako takie znajdują się w oryginalnej Perl Apocalypse.
JDługosz
0

Uważam, że wyrażenia regularne zostały zaprojektowane tak, aby były jak najbardziej „ogólne” i tak proste, jak to możliwe, dzięki czemu można ich używać (z grubsza) w ten sam sposób w dowolnym miejscu.

Twój przykład regex.isRange(..).followedBy(..)jest powiązany ze składnią określonego języka programowania i być może zorientowanym obiektowo (łańcuchem metod).

Jak na przykład wyglądałoby to „wyrażenie regularne” w C? Kod musiałby zostać zmieniony.

Najbardziej „ogólnym” podejściem byłoby zdefiniowanie prostego, zwięzłego języka, który następnie można łatwo osadzić w dowolnym innym języku bez zmian. I to jest (prawie) regex.

Aviv Cohn
źródło
0

Silniki wyrażeń regularnych kompatybilne z Perl są szeroko stosowane, zapewniając zwięzłą składnię wyrażeń regularnych zrozumiałą dla wielu edytorów i języków. Jak zauważył @ JDługosz w komentarzach, Perl 6 (nie tylko nowa wersja Perla 5, ale zupełnie inny język) próbował uczynić wyrażenia regularne bardziej czytelnymi, budując je z indywidualnie zdefiniowanych elementów. Na przykład, oto gramatyka przykładowa do analizowania adresów URL z Wikibooks :

grammar URL {
  rule TOP {
    <protocol>'://'<address>
  }
  token protocol {
    'http'|'https'|'ftp'|'file'
  }
  rule address {
    <subdomain>'.'<domain>'.'<tld>
  }
  ...
}

Podział wyrażenia regularnego w ten sposób pozwala na indywidualne zdefiniowanie każdego bitu (np. Ograniczenie domainalfanumeryczne) lub rozszerzenie na podklasę (np. FileURL is URLTakie ograniczenia protocolmają być tylko "file").

Tak więc: nie, nie ma technicznego powodu zwięzłości wyrażeń regularnych, ale są już dostępne nowsze, czystsze i bardziej czytelne sposoby ich reprezentowania! Mamy nadzieję, że zobaczymy kilka nowych pomysłów w tej dziedzinie.

Gauraw
źródło