Język programowania Clojure

Clojure to język programowania, który jest dialektem Lispu działającym pod kontrolą maszyny wirtualnej Javy (JVM). Jest językiem ogólnego przeznaczenia, dynamicznie typizowanym i wieloparadygmatowym z silnym akcentem funkcyjnym. Jego wiodącymi cechami są: dobrze opracowana obsługa wykonywania współbieżnego i niemutowalność danych (realizowana z użyciem struktur trwałych).

Polimorfizm to zbiór mechanizmów, dzięki którym te same konstrukcje języków programowania mogą być używane w odniesieniu do danych różnych rodzajów. Dzięki niemu możemy przetwarzać informacje z użyciem wyabstrahowanych, generycznych operacji, zyskując na czasie i czytelności kodu. Polimorfizm w Clojure to przede wszystkim obsługa wieloargumentowości, multimetod, protokołów i rekordów, a także korzystanie z interfejsów Javy.

Makra to jeden z mechanizmów metaprogramowania – zbioru technik umożliwiających odczytywanie, tworzenie i modyfikowanie programów przez inne programy lub przez nie same. Są one jedną z charakterystycznych cech dialektów języka Lisp, pozwalając na przekształcanie kodu źródłowego programu zanim dojdzie do jego ewaluacji. Dzięki makrom jesteśmy budować tzw. języki dziedzinowe, dostosowane do wyrażania specyficznych rozwiązań problemów w zwięzły i przejrzysty sposób.

Korzystanie z dodatkowych wątków i sterowanie wykonywaniem bieżącego pozwalają precyzyjnie zarządzać współbieżnym realizowaniem zadań. W Clojure możemy w tym celu użyć dodatkowych typów referencyjnych: Future, Promise i Delay. Istnieją również odpowiednie klasy Javy realizujące podobne cele, a nawet typ Volatile, który pozwala tworzyć szybkie choć niebezpieczne odpowiedniki zmiennych.

Ref to typ referencyjny, dzięki któremu można wyrażać częste i jednoczesne zmiany stanów wielu współdzielonych tożsamości w synchroniczny sposób. Wykorzystywany jest tam, gdzie kilka wartości wskazywanych przez referencje zależy od siebie i modyfikacja ich wszystkich powinna być atomowa (np. przekazywanie środków między rachunkami bankowymi). Do obsługi Refów intensywnie wykorzystywana jest programowa pamięć transakcyjna (STM).

Agent to typ referencyjny podobny do Atomu. Dzięki niemu można wyrażać częste, niekoordynowane zmiany stanów współdzielonych tożsamości w asynchroniczny sposób. Wykorzystywane są na przykład do obsługi niezależnych zdarzeń czy jednokierunkowej komunikacji między komponentami realizującymi dostęp do jednego zasobu (równoległe pobieranie danych z sieci, zapis do jednego pliku przez wiele wątków itp.).

Wykonywanie współbieżne pozwala spożytkować moc obliczeniową więcej niż jednego procesora lub rdzenia, pomaga też w lepszym zarządzaniu czasem obliczeniowym, gdy niektóre operacje muszą oczekiwać na obsługę komunikacji międzyprocesowej lub podsystemu wejścia/wyjścia. Clojure proponuje przejrzystą i wygodną implementację wykonywania współbieżnego, wykorzystującą typy referencyjne i programową pamięć transakcyjną.

Clojure jest językiem o solidnych podstawach teoretycznych. W tej części zajmiemy się metafizyką tego dialektu Lispu, a dokładniej definicjami stanu, tożsamości, powiązania i wartości. Pozwoli nam to oswoić się z paradygmatem funkcyjnym i zrozumieć dlaczego niektóre oczywiste czynności wymagają przeprowadzania operacji niestosowanych w innych językach programowania.

Powiązania pozwalają identyfikować pamięciowe obiekty, z których korzystamy w programach (nadawać im stałe tożsamości), a przestrzenie nazw umożliwiają zarządzanie widocznością i kapsułkowanie fragmentów kodu źródłowego. W tym odcinku dowiemy się, jak rozumieć te mechanizmy w Clojure i jak ich używać.

Typy danych pozwalają klasyfikować wartości pod względem różnych cech i wykształcać relacje między tak powstałymi klasami. Programiście pomaga to definiować operacje przeprowadzane na danych różnych rodzajów, a mechanizmom języka zarządzać pamięcią i wykrywać niektóre rodzaje błędów. W Clojure mamy do czynienia z kilkoma powiązanymi ze sobą systemami typów, które możemy rozszerzać, a wykorzystując ich polimorficzne mechanizmy jesteśmy w stanie abstrahować zarządzanie danymi i budować ujednolicone interfejsy wymiany informacji.

Specyficzna składnia dialektów języka Lisp pozwala precyzyjnie określać i rozróżniać podstawowe konstrukcje, dodawać nowe elementy syntaktyczne, a nawet przekształcać kod programu w trakcie jego pracy. Wynika to z zastosowania prostych, jednak przemyślanych sposobów organizowania i reprezentowania kodu źródłowego.

Clojure, jak każdy dialekt Lispu, wyposażono w interaktywną konsolę, dzięki której możemy na bieżąco eksperymentować i sprawdzać naszą wiedzę. Zanim więc przejdziemy do teoretycznych podstaw języka, pozwolimy sobie na praktyczny kontakt z jego mechanizmami. Omawiane konstrukcje będą wtedy przywoływały w nas praktyczne, a nie tylko abstrakcyjne, skojarzenia.