Posts tagged ‘enums’

We Don’t Need No Enums (in Python)

2013-05-13 21:48

Last weekend I attended the PyGrunn conference, back in the good ol’ Netherlands. It was a very enjoyable and instructive event, featuring not only a few local speakers but also some prominent figures from the Python community – like Kenneth Reitz or Armin Ronacher. Overall, it was a weekend of some great pythonic fun.

…Except for a one small detail. As I’ve learned there, Python will apparently get to have enums in the 3.4 version of the language. To say that this was baffling to me would be a severe understatement. Not only I see very little need for such a feature, but I also have serious doubts whether it fits into the general spirit of Python language.

Why so? It’s mostly because of the main purpose of enumeration types, derived from their usage in many languages that already have them as a feature. That purpose is to turn arbitrary data – mostly integers and strings – into well-known, reliable entities that can be safely manipulated inside our programs. Enums act as border guardians, filtering out unexpected data and converting expected data into its safe representation.

What’s safety in this context? It’s type safety, of course. Thanks to enumeration types, we can be certain that a particular value belongs to specific and constrained set of elements. They clearly define all the variants that our code should handle, because everything else was already culled at the conversion stage.

Problem is, in Python there is nothing that would guarantee those safety promises are actually fulfilled. True, the basic property of enum types is retained: given an enum object, we know it must belong to a preordained set of entities. But there is nothing that ensures we are dealing with an enum object at all – short of us actually checking that ourselves:

  1. if isinstance(state, State):

How this is different from a straightforward membership check:

  1. if state in STATES:

except for the latter looking cleaner and more explicit?…

This saying, I do not claim enums are completely out of place in Python. This is untrue, if simply because of the fact they are easily implemented through a rather simple metaclass. In fact, this is exactly how the proposed enums.Enum base is supposed to work.
At the same time, it is also possible to provide some of the before-mentioned type safety. Just look into various libraries that enhance Python with support for contracts: a form of type safety which is even more powerful than what you’ll find in many statically typed languages. You are free to use them, and you should definitely do, if your project would benefit from such a functionality.

Incidentally, they fit right in with that new & upcoming enumeration types from Python 3.4. It remains to be seen what it means exactly for the overall direction of the language design. But with enums in place, the style of “checked” typing suddenly became a lot more pythonic than it was before.

And I can’t say I like it very much.

Tags: , , ,
Author: Xion, posted under Events, Programming » 3 comments

Typy wyliczeniowe w Pythonie

2011-02-24 23:52

W Pythonie jest wiele konstrukcji językowych, które wydawać się mogą dziwne dla programistów przyzwyczajonych do innych języków. O kilku z nich już pisałem, a o paru innych pewnie zdarzy mi się jeszcze napomknąć. Dzisiaj jednak chcę wspomnieć o mechanizmie dobrze znanym z wielu innych języków, którego Python nie posiada w ogóle i jakoś sobie z tym brakiem radzi. Mam tu na myśli tytułowe typy wyliczeniowe, czyli enumy.

Jeśli zazwyczaj programujemy w językach kompilowanych ze ściśle kontrolowanymi typami, taki brak może się wydawać co najmniej irytujący. Wiemy oczywiście, że podobną funkcję może pełnić zestaw odpowiednich stałych, ale odpowiedniość nie jest zwykle dokładna – w Javie czy C# konstrukcja enum tworzy na przykład dodatkowy zasięg. Lecz nie jest to jedyna i prawdopodobnie też najważniejsza różnica.
Zdaje mi się raczej, że kluczową cechą typów wyliczeniowych jest to, że definiują one tylko pewien abstrakcyjny zbiór możliwości – bez konieczności ustalania, czym dokładnie jest każda z nich. Naturalnie wiadomo, że “pod spodem” są to po prostu liczby (aczkolwiek w Javie jest trochę inaczej), ale nie musimy się zastanawiać, skąd się one wzięły. Nie musimy nawet wiedzieć, do jakiego typu liczbowego one należą, choć niekiedy (np. w C#) możemy to doprecyzować.

Ta ostatnia cecha nie jest jednak niczym niezwykłym w języku o dynamicznym typowaniu, takim jak Python. Nieokreśloność typu dotyczy tu bowiem każdej zmiennej i dlatego nie za bardzo pasuje tu koncepcja ograniczania jej wartości do jakiegoś z góry ustalonego zbioru. Technicznie rzecz ujmując, nie bardzo też da się to zrobić.
Podobnie niezbyt pasującą do Pythona koncepcją jest sterowanie logiką za pomocą zbioru wariantów wziętych “znikąd”, czyli stałych wyliczeniowych o automatycznie generowanych wartościach. Brak w tym języku instrukcji switch jest pewnie również konsekwencją odejścia od tego rodzaju abstrakcji. Założenie jest raczej takie, aby w miarę możliwości operować na surowych danych i nie dokonywać na nich żadnych pojęciowych “wygładzeń”. Ma to sens, gdyż dwa podstawowe cele abstrahowania wartości na zbiór przypadków – upraszczanie API i zwiększanie efektywności – niespecjalnie aplikują się do Pythona.

Jak to jednak bywa w prawdziwym świecie, coś w rodzaju typów wyliczeniowych przydaje się czasami mimo wszystko. Odpowiedzią jest wtedy rzeczywiście zestaw stałych, zapewne zgrupowanych pod szyldem wspólnego zasięgu klasy. Ponieważ musimy nadać im wartości, możemy zadbać o to, by bezpośrednio odnosiły się do danych, które przetwarzamy. A jeśli w skrajnym przypadku trzeba faktycznie wziąć je z powietrza, wystarczy zastosować poniższy idiom z rozpakowywaniem range‘a:

  1. class SomeEnum(object):
  2.     FOO, BAR, LULZ, KEK = range(4)

Istnieją oczywiście bardziej wyrafinowane rozwiązania, pozwalające chociażby na iterowanie po wszystkich nazwach i wartościach naszego enuma. Sądzę jednak, że podobna funkcjonalność jest przydatna raczej rzadko.

Tags: , ,
Author: Xion, posted under Programming » 1 comment

Po co jest FORCE_DWORD

2010-05-12 15:28

Przeglądając dokumentację do DirectX (a przynajmniej do części graficznej) można natknąć się na wiele typów wyliczeniowych. Większość z nich (a może wszystkie?) na końcu swojej definicji ma stałą o nazwie kończącej się na _FORCE_DWORD. Przykładem jest znany, lubiany i przez wszystkich używany D3DRENDERSTATETYPE:
typedef enum D3DRENDERSTATETYPE
{
// (co najmniej 1<<8 różnych stałych) D3DRS_FORCE_WORD = 0x7fffffff; } D3DRENDERSTATETYPE, *LPD3DRENDERSTATETYPE;[/cpp] Zjechanie na sam dół pomocy pouczy nas, że stała ta zasadniczo... nie jest używana. Jednocześnie jednak ma ona wymuszać kompilację typu wyliczeniowego jako 32-bitowego. O co tutaj właściwie chodzi? Kompilatory C++ mogą mianowicie wybierać dla typów wyliczeniowych właściwie dowolne typy liczbowe - byle tylko wszystkie wartości stałych się zmieściły. To sprawia, że wielkość enuma może się różnić nie tylko między kompilatorami, ale i między różnymi ustawieniami kompilacji. Nietrudno na przykład wyobrazić sobie, że przy optymalizacji szybkości ów enum będzie miał rozmiar równy słowu maszynowemu, zaś przy optymalizacji zajętości pamięci będzie to rozmiar najmniejszy możliwy.
No i tu zaczynają się schody tudzież pagórki. Zmienność (a raczej niezdefiniowanie) wielkości typu wyliczeniowego jest może kłopotliwa w pewnych sytuacjach. Trudno byłoby na przykład przewidzieć to, w jaki sposób należy odczytać wartość zwróconą przez metodę GetRenderState urządzenia, która jest zapisana w 32-bitowym DWORD-zie, jeśli nie zajmowałaby ona w nim wszystkich 4 bajtów. Podejrzewam też, że na którymś etapie renderowania we wnętrzu DirectX określony rozmiar pewnych flag (np. typów prymitywów) jest po prostu wymuszany przez sterownik karty graficznej. Całkiem rozsądne jest więc zapewnienie go od samego początku – czyli już w kodzie pisanym przez programistę-użytkownika DirectX.

Czemu jednak potrzebny jest takich hack? Ano tutaj znowu wychodzi niedookreślenie pewnych rzeczy w standardzie C++, zapewne z powodu źle pojętej przenośności. Częściowo zostanie to naprawione w przyszłej wersji standardu, gdzie – podobnie jak np. w C# – możliwe będzie określenie typów liczbowych używanych wewnętrznie przez enumy.

Tags: , , , ,
Author: Xion, posted under Programming » Comments Off on Po co jest FORCE_DWORD

Wyliczenia kwalifikowane

2008-07-01 23:26

W C++ typy wyliczeniowe deklaruje się zwykle poprzez coś podobnego do poniższego kawałka kodu:

  1. enum Sides { Left = -1, Middle = 0, Right = 1 };

Jego skutkiem jest jednak to, że nazwy stałych typu (tutaj: Left, Middle i Right) będą widoczne w całej przestrzeni nazw zawierającej daną deklarację enum. Jeśli więc przypadkiem jest ona globalna, to całkiem łatwo może ona spowodować konflikt chociażby z innym typem w rodzaju:

  1. enum Keys { Left, Right, Up, Down, /* ... */ };

Aby zapobiegać takim sytuacjom, w Javie i C# stałe wyliczeniowe muszą być kwalifikowane nazwą odpowiedniego typu – używa się więc Sides.Left i Keys.Left. W C++ jest jednak inaczej, gdyż blok enum sam w sobie nie tworzy zasięgu (w przeciwieństwie np. do bloków class).

Można temu częściowo zaradzić w następujący sposób:

  1. struct Sides
  2. {
  3.     enum _Enum { Left = -1, Middle = 0, Right = 1 };
  4. };
  5. typedef Sides::_Enum Side;

dzięki czemu możemy z naszego enuma korzystać tak:

  1. Side foo;
  2. foo = Sides::Left;  // OK
  3. foo = 1; // błąd - nie można przypisać liczby
  4. foo = Right; // błąd - Right nie jest w przestrzeni globalnej

Różnica względem wspomnianych dwóch języków polega na tym, że nazwa typu wyliczeniowego (Side) oraz kwalifikator stałych (Sides) nie są takie same. Wydaje się jednak (przynajmniej mi się tak wydaje :]), że w tym przypadku takie rozróżnienie jest logicznie poprawne i wygląda nawet czytelniej niż gdyby obie nazwy były identyczne.
Trik ten można naturalnie opakować w makro, które umożliwi łatwe tworzenie typów wyliczeniowych z kwalifikowanymi nazwami stałych. Nie poprawi to oczywiście funkcjonalności enumów w C++, ale przynajmniej sprawi, że będą ładniej wyglądały :D

Tags: , ,
Author: Xion, posted under Programming » 10 comments

Wyliczenia na sterydach

2007-12-28 21:57

Co można powiedzieć o typach wyliczeniowych (enums)? Choćby to, że przydają się tam, gdzie mamy do czynienia ze stanami lub atrybutami, które należą do jakiegoś skończonego zbiory. Z technicznego punktu widzenia to kilka(naście/dziesiąt/set) liczb nazwanych stałymi identyfikatorami. I zazwyczaj języki programowania nie oferują wiele ponad to.
Lecz Java musiała się tu wyróżnić :) Z wierzchu jej enumy wyglądają niby zwyczajnie:

  1. public enum Answer { YES, NO, DONT_KNOW, DONT_CARE }

ale w środku kryją bardzo szerokie i pewnie trochę udziwnione możliwości…

Zacznijmy od tego, że takie stałe wyliczeniowe są bezpieczne pod względem typowania – nie można więc jawnie rzutować ich na zwykłe liczby typu int. Słowo kluczowe enum tworzy też – podobnie jak w C# – przestrzeń nazw, więc do stałych odwołujemy się nazwami kwalifikowanymi (Answer.YES) i nie trzeba stosować żadnych przedrostków.
Ale to są wręcz oczywistości. Kolejnym punktem programu jest możliwość dostępu do zbioru zadeklarowanych stałych oraz ich wypisywania w postaci tekstowych nazw:

  1. System.out.println ("[Poll] Are enums in Java too exaggerated?");
  2. for (Answer answer : Answer.values())
  3.    System.out.printf ("( ) %s\n", answer);

Takie nazwy mogą być jednak brzydkie. Żaden problem, bowiem w Javie z każdą stałą może być związany dowolny zestaw danych:

  1. public enum Answer
  2. {
  3.    YES("Yes"), NO("No"), DONT_KNOW("I don't know"), DONT_CARE("I don't care");
  4.  
  5.    // deklaracja dodatkowych danych w postaci pól
  6.    private final String message;
  7.  
  8.    // konstruktor (!)
  9.    Answer(String message)   { this.message = message; }
  10.  
  11.    // metoda dostępowa
  12.    public String getMessage()   { return message; }
  13. }

które – jak widać – deklarujemy podobnie jak składniki klas. A poszczególne “stałe” typu wyliczeniowego nie są już na pewno tylko liczbami: to pelnoprawne obiekty:

  1. System.out.println ("[Poll] Is this poll much better?");
  2. for (Answer answer : Answer.values())
  3.    System.out.printf ("( ) %s\n", answer.getMessage());

Widzimy, że metody są również dozwolone. Żeby było śmieszniej, mogą być one nawet “wirtualne”. Możemy bowiem zapewnić, aby inne ich wersje były wywoływane dla różnych stałych. Robi wrażenie, prawda?…

Cóż, pewnie niekoniecznie – zwłaszcza, że nietrudno osiągnąć podobną funkcjonalność przez instrukcję wyboru. Zgoda, będzie ona bardziej podatna i trudniejsza w konserwacji. Lecz jeśli alternatywą jest przeładowanie języka dziwnymi i nie do końca oczywistymi mechanizmami, to stary dobry switch niekoniecznie musi być rozwiązaniem złym.
Co oczywiście nie zabrania nam być pod wrażeniem pomysłowości twórców języka Java :-)

Tags: ,
Author: Xion, posted under Programming » 12 comments
 


© 2023 Karol Kuczmarski "Xion". Layout by Urszulka. Powered by WordPress with QuickLaTeX.com.