Pull to refresh

От PHP к Clojure

Reading time7 min
Views23K


Clojure (произносится как closure) — современный диалект Лиспа, язык программирования общего назначения с динамической типизацией и поощряющий функциональное программирование. Автор языка Рич Хикки впервые представил своё творение в 2007 году, с тех пор язык заматерел и достиг версии 1.7, вышедшей 30 июня 2015 года.

Одна из основных концепций языка — это работа на какой-либо существующей платформе. Рич Хикки решил не писать свою среду выполнения, свой сборщик мусора и т.п. — это потребовало бы больших трудозатрат, проще использовать уже готовые платформы, такие как JVM, .NET, JavaScript. На сегодняшний день два самых активно развиваемых направления: Clojure на JVM (именно он достиг версии 1.7 не так давно) и ClojureSrcipt — подмножество языка, которое компилируется в JavaScript для работы в браузере и на Node.js. Версия для .NET развивается не так активно и отстаёт от JVM имплементации. Я поискал в интернете и нашел ещё несколько мёртвых реализаций Clojure: на Go, на PHP, на Python и на Perl.

В этой статье я хочу рассказать о Clojure, показав примеры в сравнении с PHP, основываясь на серии скринкастов на английском From PHP to Clojure.

PHP и Clojure — два совершенно разных языка. Когда вы в первый раз увидите Clojure, вы можете подумать, что это какой-то наркоманский JSON. На самом деле, Clojure очень мощный и элегантный язык.

Сравнивая с PHP, многие аспекты языка имеют прямые аналоги. Другие станут понятны, если посмотреть на них под правильным углом.

Anonymous functions, Closure & Clojure


Начну с небольшого отступления, чтобы разобраться в терминологической путанице. Анонимные функции и замыкания появились в PHP 5.3, и, если внимательно посмотреть на документацию, то увидим, что анонимные функции в PHP реализованы с помощью класса Closure. Слово «Closure» в свою очередь переводится как «замыкание» и по написанию очень похоже на название языка Clojure (обратите внимание на букву j в середине). Чтобы нам не запутаться дальше, будем использовать термин «Анонимная функция» для анонимных функций и «Замыкание» для эффекта лексической видимости переменной — это когда в PHP в описании анонимной функции используется конструкция use(...). В языке Clojure, соответственно, также есть анонимные функции и замыкания.

Namespaces


Здесь очень много общего между PHP и Clojure: пространства имён состоят из частей, которые соответствуют физическому расположению файлов. Всего два отличия: в Clojure разделителем является точка и имена пространств имён принято называть с маленькой буквы.

// PHP
namespace Foo\Bar\Baz;


;; Clojure
(ns foo.bar.baz)


Синтакс


В Clojure имя функции и её аргументы находятся внутри скобок, например
(count foo) ;; Clojure
, что эквивалентно
count($foo) // PHP


Теперь посмотрим на более сложный код:



С первого взгляда ничего не понятно! Но тут есть небольшой трюк: представьте xml-подобный язык шаблонизатора, в котором есть теги if, for или тег присваивающий значение переменной:



Теперь заменим угловые скобки на круглые скобки:



Немного упросим, убрав имена атрибутов, лишние кавычки и некоторые теги:



И в тоге получаем Clojure! (на самом деле, это ещё не Clojure, но уже очень близко):



Немного попрактиковавшись, вы обнаружите, что такой синтаксис даже удобнее, чем старый-добрый классический Си-подобный. Синтаксис Clojure компактный, выразительный и достаточно легко читаемый.

Единственное, что может смущать, это количество закрывающих скобок в конце, которые принято располагать на одной строке:



Однако, в реальной жизни это не проблема, т.к. все современные редакторы и IDE умеют подсвечивать парные скобки.

Возвращаясь к PHP, его синтаксис всем нам кажется достаточно простым. Но давайте посчитаем!

Существует отдельный синтаксис для определения переменных, определения функций и определения глобальных переменных внутри функций:



Синтаксис для конструкций if, switch и других управляющих структур:



Синтаксис для определения классов, свойств и методов внутри класса, синтаксис для создания экземпляров класса, доступа к свойствам и вызова методов:



А также синтаксис для анонимных функций и замыканий:



В Clojure, напротив, синтаксис очень прост! Нужно знать три основные вещи: Value, Symbol и List (это не 100% синтаксиса, но по большей части вы будете работать именно с этими понятиями).

Value (значение) — это данные, такие как число, строка или регулярное выражение:

2
"Hello, World"
#"\d+"

Symbol (символ) — это имена, имена переменных, функций и т.п. Обычно символы указывают на какие-то данные (на Value):
def
map
db
my-symbol

List (список) — это пара круглых скобок, между которыми могут находится значения (value), символы (symbol) или другие списки (list). Т.е. можно создавать вложенные списки и списки содержащие разные типы вещей: значения, символы и списки вперемешку.

(some-symbol "Some Value" ("nested list with value" and-symbol))

И это весь синтаксис Clojure!

Но подождите, а где же описания функций, управляющие структуры (типа if/else), циклы и прочее?

Всё это присутствует в Clojure, но в рамках того общего синтаксиса, о котором я только что рассказал.

Например, if не существует как отдельная синтаксическая конструкция, это просто список, содержащий символ if, за которым следует условие, затем что выполнить при в случае истины и что выполнить в противном случае. Причём, всё эти элементы конструкции в свою очередь являются либо значением, либо символом, либо списком:


Тоже самое с функциями. Функция определяется как список, содержащий значения, символы и другие списки. Аналогичная ситуация с определением переменных, пространств имён и всего прочего. Иными словами, вместо того, чтобы вводить отдельный синтаксис на каждую фичу языка, везде используется универсальный синтаксис из списков, символов и значений.

Из этого вытекают дополнительные плюшки, например, теперь не нужно запоминать приоритет операций.

Приоритет операций в PHP:


Чтобы сложить два числа, нужно открыть скобку, затем написать символ +, затем через пробел два числа требующих сложения и закрыть скобку:

(+ 3 4)

— получился список состоящий из символа и двух значений. Знак + является валидным символом в Clojure и он ссылается на функцию, которая выполняет сложение последующих элеменотов в списке.

Другой пример с приоритетом операций: 4 + 3 / 2 = ?

В зависимости от того, какой приоритет вы действительно хотите, вы напишете либо так:

(/ (+ 4 3) 2) ;; 3.5

Либо так:

(+ 4 (/ 3 2)) ;; 5.5

и здесь нет никакого автоматического приоритета операций, вы всегда явно указываете порядок выполнения действий.

Символы


Символы в Clojure предназначены для именования разных вещей. Обратите внимание, что их не принято называть переменными, т.к. данные в Clojure неизменяемы по умолчанию. Иными словами, символы указывают на неизменяемые данные.

По соглашению принято писать имена символов маленькими буквами, разделяя слова через дефис. Символы не могу начинаться с цифры, в остальном доступны следующие знаки:



Булевы значения принято заканчивать знаком вопроса (в PHP такие переменные обычно начинаются с префикса is):



Помимо списка разрешенных знаков для использования в именах символов, стоит также упомянуть и зарезервированные, которые не могут быть частью имени:



Некоторые знаки находятся в «серой зоне» — их можно использовать в именах символов в текущей версии Clojure, но нет гарантии, что они однажды не станут зарезервированными:



Скалярные типы данных



Числа


Как и в PHP, в Clojure есть целые числа и числа с плавающей запятой. Однако, в отличие от PHP, существует целых два типа целых чисел: Integer и BitInt. Второй тип может хранить сколь угодно большие значения, на сколько хватает оперативной памяти. Чтобы явно указать компилятору, что нужно использовать BigInt, нужно поставить большую букву M после числа. Аналогичная ситуация в числах с плавающей запятой, но там используется большая буква N в конце.



Также можно использовать разные системы счисления:



Clojure поддерживает работу с дробями! Сравните код на PHP:

$x = 4 / 3; //результат 1.33333...
$y = $x * 3; //результат 3.99999....

И на Clojure:

(/ 4 3) ;; результат - дробь 4/3 представленная специальным типом данных Ratio
(* 4/3 3) ;; результат 4


Строки


Строки в Clojure — это Java строки. Они не поддерживают интерполяцию, т.е. нельзя просто так взять и вставить внутрь какую-нибудь переменную в середине строки, но можно использовать специальные последовательности, типа \nдля перевода. Кроме того, строки могут быть многострочными:



В отличие от PHP, строки нельзя заключать в одинарные кавычки:



В Clojure есть отдельный тип Character — односимвольные «строки», они записываются без кавычек, но с обратным слешем в начале. Обратите внимание, что \n — это не перевод строки, это просто буква n.

Существует также набор предопределённых Characters, которые как раз используются для определения перевода строки, табуляции и т.п.: \newline, \tab, \backspace.

Также можно получить отдельные unicode символы, например, \u263a.

Наконец, доступна и восьмеричная запись: \o003 — это Ctrl+C.



Регулярные выражения


Регулярные выражения начинаются с символа # и затем само выражение в кавычках: #"\d+". Под капотом используются регулярные выражения из Java, поэтому обратите внимание на Java-синтаксис:



Ещё немного о скалярных типах


Тип Nil может принимать единственное значение nil (аналогично типу Null в PHP).

Тип Bool принимает два значения true и false. Однако, в отличие от PHP, только два значения nil и false воспринимаются как ложные. Для сравнения, значение 0 и "" (пустая строка) в Clojure будут восприняты как истина, в то время как в PHP они будут ложью:



Keyword


В Clojure существует тип данных под названием Keyword, но это вовсе не те самые ключевые слова к которым мы привыкли в PHP (вроде if, for и т.п.). Keywords всегда начинаются с символа двоеточие. Вы сами создаёте ключевые слова в коде программы и их единственное значение — это они сами. Нельзя присвоить какое-то значение ключевому слову. На скриншоте ниже единственное возможное значение ключевого слова :pi — это само ключевое слово :pi.



Но зачем нужны ключевые слова, если им нельзя присвоить значение? В PHP существует конструкция define для определения глобальных константных значений. Зачастую, сами значения определённые в define, не имеют смысла, мы хотим лишь определить какое-то зарезервированное имя, чтобы использовать его в качестве ключа массива или в качестве параметра функции.

Например, в PHP есть функция str_pad, которая дополняет одну строку другой строкой до заданной длины. Последний параметр этой функции это $pad_type принимающий одно из трёх значений: STR_PAD_RIGHT, STR_PAD_LEFT, STR_PAD_BOTH. Под капотом эти три константы имеют значения 0, 1 и 2 соотвественно. На самом деле они могли бы иметь любые значения, типа 265, 1337 и 9000 — это не важно.

В Clojure мы использовали бы keywords :str-pad-right, :str-pad-left, :str-pad-both — они не имеют каких-то других значений под капотом, они равны сами себе и это именно то, что нужно!

Ещё чаще, ключевые слова можно встретить в ассоциативных массивах:
{:first-name "Irma", :last-name "Gerd"}


Но тема ассоциативных массивов и других типов данных для работы с коллекциями выходит за рамки данной статьи.

Вместо заключения — полезные ссылки


Надеюсь я заинтересовал вас языком Clojure, ведь на нём можно делать отличные веб-приложения, о чём можно прочитать в паре статей опубликованных недавно на хабре: «Веб-приложения на Clojure» и «Веб-приложение на Clojure. Часть 2».

Поскольку фокус статьи был на сравнение синтаксиса Clojure и PHP, то отдельно выделю ссылку на таблицу с примерами базовых выражений PHP vs Clojure.

Приходите послушать и задать свои вопросы живьём на конференции FPCONF 15 августа 2015 года в Москве, где будут доклады по веб-разработке на Clojure и ClojureScript.
Tags:
Hubs:
Total votes 30: ↑22 and ↓8+14
Comments138

Articles