Książka "The Pragmatic Programmer" (jeśli jeszcze jej nie przeczytałeś, przestań czytać ten artykuł i zrób to teraz!) mówi, że każdego roku powinniśmy uczyć się jednego nowego języka programowania.
Chociaż niektórzy mogą twierdzić, że to zbyt duży wysiłek, wszyscy możemy się zgodzić, że może to być dobry pomysł. Wybór nowego języka do nauki nie jest taki łatwy. Nie chcemy poświęcać czasu na coś, czego możemy nigdy nie użyć w praktyce, prawda? Ale może czasami powinniśmy zrobić wyjątek i nauczyć się czegoś dla przyjemności? Chciałbym zaprezentować język Brainfuck. Jest to język, którego można nauczyć się w kilka minut, więc nie ma problemu z inwestowaniem zbyt dużej ilości czasu na próżno. Mogę również obiecać, że rozwiązywanie dowolnego problemu za pomocą Brainfuck będzie stymulować twój mózg (wszystkie f*cks są tylko bonusem ;)). Zaczynajmy! Według Wikipedii:
Brainfuck jest ezoteryczny język programowania stworzony w 1993 roku przez Urbana Müllera. Język ten składa się tylko z ośmiu prostych poleceń i wskaźnika instrukcji. Choć jest on w pełni kompletny pod względem Turinga, nie jest przeznaczony do praktycznego użytku, ale do stawiania wyzwań i zabawiania programistów.
Przegląd języka
Wyobraźmy sobie nieskończenie długą wstęgę (lub taśmę) składającą się z komórek, z których każda ma wartość początkową 0. Istnieje również ruchomy wskaźnik danych, który początkowo wskazuje na pierwszą komórkę. Istnieją również dwa strumienie bajtów dla wejścia i wyjścia. Instrukcje są wykonywane sekwencyjnie, jedna po drugiej. Maszyna zatrzymuje się po wykonaniu ostatniej instrukcji.
Polecenie
Co robi?
>
przesunięcie wskaźnika danych do następnej komórki po prawej stronie
<
przesunięcie wskaźnika danych do następnej komórki po lewej stronie
+
przyrost wartości bieżącej komórki
–
zmniejszenie wartości bieżącej komórki
.
wyświetla bajt aktualnie wskazywanej komórki w ASCII kod
,
odczytuje jeden bajt ze stdin i zapisuje jego wartość w bieżącej komórce
[
jeśli bieżąca komórka ma wartość 0, przeskocz do pasującej komórki ]
]
przeskoczyć do pasującego [
Wszystkie znaki inne niż ><+-.,[] są ignorowane.
Spójrzmy na prosty przykład:
,+.
Będzie on interpretowany w następujący sposób:
odczytać jeden bajt i zapisać go w bieżącej komórce (komórka0)
przyrost wartości bieżącej komórki (komórka0 = komórka0 + 1)
zapis zawartości bieżącej komórki na wyjście
W rezultacie jeden znak zostanie odczytany z wejścia i zostanie wypisany następny znak z tablicy ASCII.
Interpreter / kompilator
Zanim napiszemy jakieś użyteczne (?) programy w Brainfucku, potrzebujemy interpretera lub kompilatora. AFAIK, nie ma oficjalnego, ale to nie problem. W internecie są dziesiątki nieoficjalnych. Mogę polecić te dwa:
"Hello World!" powinien być pierwszym programem, który piszemy ucząc się nowego języka. Jednak napisanie go w Brainfucku jest nieco trudniejsze niż w innych językach. Musimy zacząć od czegoś łatwiejszego... Napiszmy program, który wypisze pojedynczą literę "H" na ekranie (takie ekscytujące :D):
Jak to działa? Ustawia wartość bieżącej komórki na 72 (wykonując 72 przyrosty) i drukuje ją na ekranie za pomocą "." (H ma kod 72 w ASCII). Teraz już wiesz, co powinniśmy zrobić, aby wydrukować "Hello World!" na ekranie, ale zanim to zrobimy, dokonamy małego refaktoryzacji. Napisanie tych wszystkich "+" wymaga zbyt dużo pisania i liczenia. Możemy to skrócić używając [ i ] dla pętli. Aby ustawić wartość na 72, możemy np. wykonać pętlę, która zwiększy wartość 7 razy o 10. W ten sposób otrzymamy 70. Dodając 2 otrzymamy 72. Wygląda to następująco:
++++++++++ # ustaw cell0 na 10
[# pętla, aż cell0 będzie równe 0
- # zmniejsz komórkę0
> # przesuń wskaźnik danych w prawo (komórka1)
+++++++ # zwiększ komórkę1 o 7
# przesunięcie wskaźnika danych w prawo (komórka1)
++ # zwiększenie o 2
. # wypisanie wyniku
Dołączyłem komentarze, aby wyjaśnić, jak wszystko działa. Ten sam program bez komentarzy:
++++++++++[->+++++++++.
Czyż nie jest piękny? 🙂
Hello World!
Wracając do naszego programu "Hello World!". Moglibyśmy ustawić wartość pierwszej komórki na 72 (H) i wydrukować ją, ustawić wartość drugiej komórki na 101 (e) i wydrukować ją, ustawić wartość trzeciej komórki na 108 i wydrukować ją itd. Oto implementacja tego algorytmu:
Tak, tylko 1120 bajtów do wydrukowania "Hello World!"... Ale możemy zrobić to lepiej! Zamiast używać nowej komórki dla każdego znaku, użyjmy tylko jednej. Aby wydrukować literę "e" (101), możemy ponownie użyć wartości w komórce 0 (72). Możemy ją zwiększyć o jeden 29 razy (101 - 72). Wynik jest następujący:
To tylko 106 bajtów i wypisuje nową linię na końcu! Niesamowite.
Odwracanie ciągu
Teraz jesteśmy gotowi napisać coś bardziej wymagającego. Napiszmy program, który odczyta linię z wejścia i wydrukuje ją w odwrotnej kolejności. Pierwszym problemem jest odczytanie znaków i zatrzymanie się na znaku nowej linii. Pamiętaj, że nie ma przerwa, jeśli lub inne podobne stwierdzenia. Musimy użyć [ i ]. Zacznijmy od programu, który odczytuje wszystkie znaki z wejścia i umieszcza je w kolejnych komórkach:
,[>,]
Rozpoczyna się od odczytania pierwszego znaku i trwa do ostatniego , zwroty operacji 0. Jednakże, będzie się on zapętlał w nieskończoność w implementacji, która zwraca coś innego niż O dla EOF (język nie określa tego zachowania). Jak więc możemy zatrzymać się na znaku nowej linii? Oto sztuczka:
+[++++++++++>,----------]
Zaczynamy od komórki 0 ustawionej na 1, aby upewnić się, że nasza pętla zostanie wykonana co najmniej raz. W pętli zwiększamy wartość bieżącej komórki o 10, przesuwamy wskaźnik danych do następnej komórki, odczytujemy jeden znak i zmniejszamy jego wartość o 10. W ten sposób, jeśli zostanie odczytany znak nowej linii (10 w ASCII), program zatrzyma się w następnej iteracji, w przeciwnym razie jego wartość zostanie przywrócona przez dodanie 10.
Po tym kroku nasze komórki będą wyglądać następująco:
11 C1 C2 C3 0* 0 0
Cn jest n-tym znakiem na wejściu, a * jest bieżącą pozycją wskaźnika danych. Teraz musimy zacząć przesuwać wskaźnik danych w lewo i drukować wszystkie komórki, aż osiągniemy wartość 11. Oto moje podejście do tego zadania:
Kiedy natknąłem się na Brainfuck, ezoteryczny język programowania, początkowo odrzuciłem go jako nic więcej niż sztuczkę lub żart. Ten osobliwy i, jak wielu może twierdzić, oszałamiająco trudny język, wydawał mi się czymś przeznaczonym wyłącznie do rozrywki. Z czasem jednak moje postrzeganie Brainfucka zmieniło się diametralnie.
Enigmatyczna natura Brainfuck rzuca ci wyzwanie, zmuszając cię do poszerzenia swojej perspektywy na języki programowania. Ten ezoteryczny język pozwala docenić piękno i konieczność języków wysokiego poziomu, do których jesteśmy przyzwyczajeni. Uwydatnia znaczenie abstrakcji, właściwych konwencji nazewnictwa i zorganizowanego układu pamięci w dziedzinie języków programowania. Jest to coś, czego Brainfuck, ze swoim minimalistycznym projektem składającym się z zaledwie ośmiu prostych poleceń, nie zapewnia.
Brainfuck to kompletny język Turinga, który jeszcze bardziej podkreśla znaczenie posiadania przejrzystego, spójnego kodu źródłowego. Pomimo tego, że jest uznawany za jeden z najtrudniejszych języków ezoterycznych do pisania programów, jak na ironię jest on ulubionym językiem początkujących dla każdego, kto chce stworzyć własny kompilator lub interpreter Brainfuck. Powodem jest prostota zestawu poleceń i fakt, że nie wymaga on skomplikowanego parsowania.
Tworzenie programu Brainfuck jest wyjątkowe na dwa sposoby. Po pierwsze, trzeba dostosować się do korzystania z pojedynczego wskaźnika pamięci, co zmusza do innego myślenia o kodzie źródłowym. Po drugie, masz "opcję zero", czyli możliwość wyzerowania komórki pamięci, co nie jest powszechne w innych formalnych językach programowania.
Jeśli chodzi o naukę, w Brainfuck jest więcej niż na pierwszy rzut oka. Mając wystarczająco dużo czasu i odpowiednie nastawienie, możliwe jest napisanie tego samego programu na wiele sposobów przy użyciu różnych kodów Brainfuck. Ostatnia połowa tej podróży polega na byciu pomysłowym i znajdowaniu nowych, kreatywnych sposobów wykorzystania sześciu symboli.
Interpretery Brainfuck, choć minimalistyczne, pozwalają dogłębnie zrozumieć, jak działa kod, co drukuje program i na czym polega mechanika kompletnego języka Turinga. Ostatecznie Brainfuck nie jest tylko kolejnym ezoterycznym językiem programowania. To zupełnie nowy wymiar, inne spojrzenie na to, jak widzimy, rozumiemy i piszemy programy.