Статьи

Быстрое погружение в язык программирования Crystal

Снимок экрана 2016-07-20 07.57.18

Возможно, вы слышали упоминания о языке программирования Crystal в последнее время. Это язык, который очень похож на Ruby. Фактически, многие Ruby-программы также являются действительными программами Crystal. Однако следует подчеркнуть, что это всего лишь побочный эффект синтаксиса языка и не является целью проекта.

Одна из самых интересных вещей в Crystal — это язык со статической проверкой типов, однако программисту не нужно разбрасывать типы везде, например в Java. Crystal компилируется в эффективный код, что означает, что программы Crystal работают намного быстрее, чем программы Ruby.

В этой статье мы кратко погрузимся в Crystal. Это ни в коем случае не исчерпывающий обзор всех функций Crystal. Вместо этого мы собираемся разработать сборщик шуток Чака Норриса с линзой Рубииста. Это включает в себя выполнение запросов HTTP GET, а также анализ JSON.

Мы увидим, как далеко мы уйдем, а также рассмотрим возможности, которые предоставляет Crystal, которые делают вещи более удобными.

Получение Кристалла

Я осторожно направлю вас к руководству по установке, поскольку оно, несомненно, будет выполнять гораздо более эффективную работу по освещению установки для различных дистрибутивов Linux, Mac OSX и других систем Unix. Для пользователей MS Windows: Извините! Crystal требует системы на основе Linux / Unix.

Последовательная версия

Сначала мы начнем с последовательной версии. Во-первых, нам нужен HTTP-клиент для извлечения шуток. Наши данные поступят из Интернет-базы данных Чака Норриса. Для того, чтобы получить шутку, вам просто нужно вызвать URL, например:

http://api.icndb.com/jokes/123 

Это возвращает:

 { "type":"success", "value":{ "id":123, "joke":"Some people wear Superman pajamas. Superman wears Chuck Norris pajamas.", "categories":[ ] } } 

Давайте создадим новый класс и chucky.cr его chucky.cr . Пока мы это делаем, мы будем реализовывать Chucky#get_joke(id) :

 require "http/client" require "json" class Chucky def get_joke(id) response = HTTP::Client.get "http://api.icndb.com/jokes/#{id}" JSON.parse(response.body)["value"]["joke"] end end c = Chucky.new puts c.get_joke(20) 

Crystal поставляется со встроенным HTTP-клиентом и парсером JSON. Давайте попробуем запустить это в терминале, что делается передачей имени файла команде crystal :

 $ crystal chucky.cr The Chuck Norris military unit was not used in the game Civilization 4, because a single Chuck Norris could defeat the entire combined nations of the world in one turn. 

Большой успех! Все идет нормально. Скажем, мы хотим получить кучу шуток. Кажется довольно просто:

 class Chucky ...other methods... def get_jokes(ids : Array(Int32)) ids.map do |id| get_joke(id) end end end 

Первое, что вы заметите, это отличается от Ruby, это то, что тип ids явно определяется как Array(Int32) . Это читается как « Array Int32 s». Чтобы было ясно, мы могли бы это исключить. Однако, поскольку я почти уверен, что ids всегда Int32 , я хочу быть предельно ясным и избегать таких ошибок, как:

 c = Chucky.new puts c.get_jokes(["20"]) 

Фактически, когда вы попытаетесь запустить это, вы получите ошибку во время компиляции:

 Error in ./chucky.cr:50: no overload matches 'Chucky#get_jokes' with type Array(String) Overloads are: - Chucky#get_jokes(ids : Array(Int32)) puts c.get_jokes(["20"]) ^~~~~~~~~ 

Проницательный читатель Chucky#get_joke вместо этого имеет смысл указывать тип аргумента в Chucky#get_joke , и она была бы абсолютно права. Фактически, вы также можете указать тип возвращаемого значения метода:

 class Chucky def get_joke(id : Int32) : String # ... end def get_jokes(ids : Array(Int32)) : Array(String) # ... end end c = Chucky.new puts c.get_jokes([20]) # <-- Change this back to an Array(Int32) 

Давай еще раз попробуем:

 % crystal chucky.cr Error in ./chucky.cr:53: instantiating 'Chucky#get_jokes(Array(Int32))' puts c.get_jokes([20]) ^~~~~~~~~ in ./chucky.cr:20: instantiating 'get_joke(Int32)' get_joke(id) ^~~~~~~~ in ./chucky.cr:24: type must be String, not JSON::Any def get_joke(id : Int32) : String ^~~~~~~~ 

Упс! Компилятор что-то поймал! Похоже, что Chucky#get_joke(id) не возвращает String , а вместо этого возвращает JSON::Any . Это здорово, потому что компилятор уловил одно из наших неверных предположений.

Теперь у нас есть два варианта. Либо мы переключаем наш Chucky#get_joke[s] чтобы вернуть JSON::Any либо продолжаем использовать String . Мой голос за String , потому что любой клиентский код не должен заботиться о том, чтобы шутки были JSON::Any . Давайте Chucky#get_joke(id) . Для правильной оценки мы также обрабатываем случай, когда есть некоторая ошибка синтаксического анализа и просто возвращаем пустую String :

 class Chucky # ... def get_joke(id : Int32) : String response = HTTP::Client.get "http://api.icndb.com/jokes/#{id}" JSON.parse(response.body)["value"]["joke"].to_s rescue "" end end 

Теперь все должно работать нормально. У нас небольшая проблема. Попробуйте представить, что происходит, когда мы делаем это:

 c = Chucky.new puts c.get_jokes[20, 30, 40] 

В нашей текущей реализации шутки будут выбираться последовательно. Это означает, что время, которое требуется Chucky#get_jokes(ids) для завершения, — это общее время, необходимое для извлечения всех трех шуток.

Мы можем сделать лучше!

Параллельная версия

Теперь, когда мы сделали это, давайте сделаем это быстро . Crystal поставляется с примитивом параллелизма, называемым волокнами , которые в основном представляют собой облегченную версию потоков. Другим примитивом параллелизма являются каналы . Если вы сделали Голанг, это в основном та же идея. Каналы — это способ связи волокон без головной боли общей памяти, блокировок и мьютексов.

Мы будем использовать как волокна, так и каналы, чтобы одновременно получать шутки.

Вот основная идея. Мы создадим канал в основном волокне. Каждый вызов Chucky#get_joke(id) будет выполняться по оптоволокну. Как только шутка получена, мы отправим каналу результат.

 class Chucky # ... def get_jokes(ids : Array(Int32)) : Array(String) # 1. Create channel in the main fiber. chan = Channel(String).new # 2. Execute get_joke in a fiber. Send the result to the channel. ids.each do |x| spawn do chan.send(get_joke(x)) end end # 3. Receive the results. (1..ids.size).map do |x| chan.receive end end end 

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

Затем мы выполняем get_joke(id) в волокне. Это делается в блоке spawn . Как только мы получим результат от get_joke(id) , мы отправим результаты на ранее созданный канал.

Отправка канала по значению — это всего лишь одна часть головоломки. Чтобы получить значение из канала, нам нужно вызвать Channel#receive . Каждый раз, когда мы вызываем функцию receive, мы возвращаем одно значение. Если нет значений (пока), он будет заблокирован. Так как мы знаем размер ids , нам просто нужно вызвать Channel#receive ids.size times.

Попробуйте запустить программу еще раз. На этот раз общее время, затраченное на самый длинный запрос.

Crystal Yay’s

Кристалл выглядит действительно впечатляюще. Мне нравится, что он статически типизирован, но система типов не мешает, тем более, что вы можете в основном обходиться без указания типов.

Будучи скомпилированным языком (в LLVM не меньше), неудивительно, что некоторые из тестов производительности, которые я видел, очень впечатляют.

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

Crystal Meh’s

Там нет REPL. Для Rubyist это кажется почти невообразимым для любого программирования на Ruby без точного IRB (или Pry).

Пока есть параллелизм, еще нет параллелизма. Это означает, что программа Crystal использует только одно ядро. Тем не менее, это все еще молодой язык, так что это не финальная история.

Наконец, сообщество Crystal имеет непростую задачу создания процветающей экосистемы, такой как Rubygems.

Спасибо за прочтение!

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

Счастливая Crystall!