I’m still flabbergasted after going through the analysis of PHP ==
operator, posted by my infosec friend Gynvael Coldwind. Up until recently, I knew two things about PHP: (1) it tries to be weakly typed and (2) it is not a stellar example of language design. Now I can confidently assert a third one…
It’s completely insane.
However, pondering the specific case of equality checks, I realized it’s not actually uncommon for programming languages to confuse the heck out of developers with their single, double or even triple “equals”. Among the popular ones, it seems to be a rule rather than exception.
Just consider that:
==
and ===
, exactly like PHP does. And the former is just slightly less crazy than its PHP counterpart. For both languages, it just seems like a weak typing failure.=
(assignment) in lieu of ==
(equality), because the former is perfectly allowed inside conditions for if
, while
or for
statements.String.equals
method rather than ==
(like in case of other fundamental data types). Many, many programmers have been bitten by that. (The fact that under certain conditions you can compare strings char-by-char with ==
doesn’t exactly help either).Equals
and overload ==
operator. It also introduces ReferenceEquals
which usually works like ==
, except when the latter is overloaded. Oh, and it also has two different kinds of types (value and reference types) which by default compare in two different ways… Joy!The list could likely go on and include most of the mainstream languages but one of them would be curiously absent: Python.
You see, Python got the ==
operator right:
is
operator.int
, long
, float
) compare to each other just fine, but there is clear distinction between 42
(number) and "42"
(string).==
but there are no magical tricks that instantly turn your class into wannabe fundamental type (like in C#). If you really want value semantics, you need to write that yourself.In retrospect, all of this looks like basic sanity. Getting it right two decades ago, however… That’s work of genius, streak of luck – or likely both.
Tytuł dzisiejszej notki nie ma nic wspólnego z miejscowością wypoczynkową na południu Chorwacji, chociaż pewnie obecne temperatury nasuwają takie skojarzenia :) Zamiast tego chodzi o split łańcucha znaków, czyli bardzo często potrzebną w praktyce operację na stringach.
Danymi dla niej są najczęściej dwa napisy, zaś wynikiem jest tablica podciągów pierwszego z nich, powstała poprzez podzielenie go względem wystąpień drugiego (tzw. separatora). Ponieważ jak zwykle przykład będzie mówił najwięcej, niniejszym podaję nawet kilka:
Zwłaszcza ostatni pokazuje, że split jest rzeczywiście użyteczną funkcją, mogącą ułatwiać parsowanie formatów tekstowych (zwłaszcza, jeśli daje się ją zastosować wielokrotnie). Warto więc mieć takową w swoim języku/bibliotece. Jak więc przedstawia się jej dostępność na różnych platformach?
Tradycyjnie w .NET i Javie jest dobrze, a nawet lepiej. W obu przypadkach funkcja (a właściwie metoda) Split
/split
klasy String
dodaje nawet trochę więcej możliwości niż to opisałem wyżej. I tak w .NET można podać więcej niż jeden oddzielacz, natomiast w Javie domyślnie może być nim również wyrażenie regularne.
Niektóre języki skryptowe i skryptopodobne też mają się w tym względnie całkiem dobrze. W Pythonie jest metoda split
w klasie napisu, natomiast PHP ma funkcję explode
, która mimo innej nazwy działa bardzo podobnie.
Ale nie wszędzie funkcja typu split jest od razu dostępna; niekiedy trzeba ją sobie samemu napisać. Przykładem języka, gdzie może być to koniecznie, jest Lua oraz – jakżeby inaczej – C++ :) Ze względu na użyteczność splita często znajdowałem się w sytuacji, gdzie konieczne/wygodne było jego napisanie. Po kilku(nastu?) takich przypadkach doszedłem wreszcie do czegoś podobnego do poniższego kodu:
typedef std::vector
StringArray Split(const std::string& text, const std::string& delim)
{
StringArray res;
if (delim.empty()) { res.push_back(text); return res; }
std::string::size_type i = 0, j;
while (i < text.length()
&& (j = text.find(delim, i)) != std::string::npos)
{
res.push_back (text.substr(i, j - i));
i = j + delim.length();
}
res.push_back (text.substr(i));
return res;
}[/cpp]
Na koniec zwrócę jeszcze uwagę na to, że czasami trzeba ostrożnie postępować z rezultatem splitu. Zdecydowana większość wersji tej operacji dopuszcza, by w wynikowej tablicy występowały puste ciągi. Odpowiadają one kilku kolejnym wystąpieniom separatora lub jego obecnością na początku bądź końcu ciągu. Jeśli nie są one nam potrzebne (a rzadko są), to należy je zignorować lub usunąć.
Pamięć kodera (ta w hipokampie, nie RAM) jest oczywiście doskonała, ale w większości wypadków także ulotna i mało pojemna. Zaglądanie do dokumentacji w poszukiwaniu każdego drobiazgu jest zaś męczące i mało efektywne. O grubych książkach, które wymagają wertowania spisu treści lub indeksu, już w ogóle nie wspominam.
Stąd np. leksykony O’Reilly, wydawane w Polsce przez Helion oraz Tablice informatyczne tego samego wydawnictwa. Zwłaszcza ten drugi pomysł jest interesujący…
Jego praktyczną (i darmową) realizację znalazłem na stronie pod zaiste wiele mówiącą nazwą AddedBytes.com (dawniej: ILoveJackDaniels.com :)). Tam też można zaopatrzyć się w przydatne tablice zwane cheat sheets.
Jest ich tam całkiem sporo, a po wydrukowaniu mogą być cenną pomocą naukową. Właściwie jedyną ich wadą jest to, że dotyczą tylko technologii webowych: HTML, CSS, PHP, i tak dalej. Tym niemniej mogą być użyteczne choćby jako wzorzec do skonstruowania swoich własnych ściągawek dla bardziej przydatnych koderom narzędzi, jak C++, DirectX, HLSL czy .NET.
Tylko kto odważny się tego podejmie?… ;-)
W języku angielskim istnieje bardzo ciekawe słowo ‘serendipity‘. W skrócie, oznacza ono “umiejętność” dokonywania szczęśliwych i ważnych odkryć całkowicie przez przypadek – w szczególności wtedy, gdy tak naprawdę szukaliśmy czegoś zupełnie innego. Najciekawszą cechą tego słowa jest fakt, że bardzo trudno przełożyć je na język inny niż angielski przy pomocy czegoś krótszego niż podana wcześniej definicja (a już na pewno nie poprzez jeden wyraz). Dlatego też w 2004 roku znalazło się ono w czołówce najtrudniejszych do przetłumaczenia słów.
Czy w językach programowania możemy spotkać się z czymś podobny? Istnieją oczywiście tzw. idiomy, nakazujące by określone czynności robić tak, a nie inaczej – jak choćby słynny idom erase
–remove
z STL. Jednak tutaj chodzi mi raczej o taki element języka, który ułatwia życie albo po prostu jest w jakiś sposób nietypowy i – co najważniejsze – nie jest po prostu cukierkiem składniowym: jego przełożenie na inny język wymagałoby większej ilości kodu lub byłoby po prostu niemożliwe.
Jeżeli odpowiedź to pytanie jest twierdząca, to moimi osobistymi typami są:
for
w C++. Można ją rzecz jasna z powodzeniem symulować we wszystkich językach proceduralnych przy pomocy pętli typu while
, lecz jej elastyczność jest naprawdę zadziwiająca. Prawie komicznie wygląda pętla, w której wszystkie operacje wykonywane są w nagłówku, a jej zasadnicza treść jest pustym blokiemlist
, pozwalająca rozdzielić elementy tablicy pomiędzy ustalone zmienne za pomocą jednej instrukcji. Przydatna choćby przy przetwarzaniu rekordów baz danych czy parametrów GET i POST. Jej odpowiednikiem w innych językach jest po prostu odpowiednia seria przypisań.
Mid
z Visual Basica. Zasadniczo jej celem jest wyodrębnienie fragmentu łańcucha znaków i zwrócenie go, lecz może ona występować też po prawej stronie operatora przypisania. Wówczas służy ona do zastąpienia tegoż fragmentu innym tekstem, np.:
initalization
i finalization
w modułach Delphi. Zawarty w nich kod jest wykonywany jednokrotnie, odpowiednio: przy załadowaniu modułu oraz przy “sprzątaniu” w trakcie kończenia aplikacji. Nie jest to za bardzo przydatne w kodzie obiektowym – tym bardziej używającym odśmiecacza pamięci – ale podobny mechanizm występuje też w języku Python.x
, y
, z
lub w
) podajemy kilka:
Na pierwszy rzut oka może to wyglądać nieco zagadkowo, ale warto tej konstrukcji używać, gdyż karty graficzne dokonują swizzlingu albo za darmo, albo bardzo małym kosztem Zatem mamy tutaj elegancję, tajemniczość i efektywność w jednym, czyli to co programiści lubią najbardziej ;)
Ta lista na pewno jest o wiele za krótka. Jestem pewien, że programiści innych języków – tych co bardziej egzotycznych – mogliby dodać mnóstwo własnych typów.