
Привет, Хабр!
История Crystal начинается в 2011 году, когда команда энтузиастов решили создать язык, который бы исправил некоторые из тех ограничений и проблем, с которыми они сталкивались, работая с Ruby. Они мечтали о языке, который бы позволял писать код, легкий для понимания и поддержки, но при этом обладающий высокой производительностью и эффективностью выполнения. Так родился Crystal, язык, который наследует синтаксис Ruby.
На первый взгляд, код на Crystal может показаться почти идентичным коду на Ruby — это было сделано намеренно, чтобы разрабы, уже знакомые с Ruby, могли без труда перейти на использование нового языка. Однако, несмотря на внешнее сходство, Crystal вносит ряд улучшений: система статической типизации с автоматическим выводом типов, обработка параллельных вычислений и возможность компиляции в машинный код.
Основной синтаксис
Crystal автоматически выводит типы переменных. Несмотря на это, можно явно указывать типы:
name : String = "Habr" age : Int32 = 10
Crystal поддерживает стандартные управляющие структуры, такие как if, else, case, а также циклы while и until:
if age >= 18 puts "Adult" else puts "Minor" end
Определение методов в Crystal очень похоже на Ruby, нво с добавлением типов аргументов и возвращаемого значения:
def add(a : Int32, b : Int32) : Int32 a + b end
ООП в Crystal реализовано через классы и модули, подобно Ruby, но с более строгой системой типов:
class Person property name : String property age : Int32 def initialize(@name : String, @age : Int32) end end
Crystal использует модель акторов и каналы для обработки конкурентных операций:
spawn do puts "Hello from the parallel universe!" end
Макросы в Crystal позволяют генерировать код во время компиляции:
macro tag(name, content) {% puts "<#{name}>#{content}</#{name}>" %} end tag("p", "Hello, Habr!")
Crystal обрабатывает ошибки через систему исключений, аналогичную Ruby, но требует явного указания возможных типов исключений:
begin # опасный код rescue ex : DivisionByZeroError puts "Cannot divide by zero!" end
Crystal может легко взаимодействовать с C:
@[Link(ldflags: "-lsqlite3")] lib LibSQLite3 fun open(filename : String, out db : SQLite3) : Int32 end
Типизация в Crystal
Статическая типизация означает, что тип каждой переменной, параметра и метода известен на этапе компиляции. Статическая типизация позволяет Crystal предотвращать целый ряд ошибок еще до запуска программы.
Однако статическая типизация часто ассоциируется с необходимостью явного указания типов, и все это фиксится с помощью инференции типов — способности компилятора автоматически определять типы на основе контекста использования переменных и выражений.
Рассмотрим простой пример:
def add(a, b) a + b end puts add(1, 2) # 3
Здесь не указаны типы для параметров a и b, но благодаря инференции типов Crystal понимает, что оба параметра и результат их сложения должны быть целыми числами, по дефолту в основном Int32.
Если попытаться сложить число и строку:
puts add(1, "two")
Компилятор Crystal выдаст ошибку, поскольку не сможет найти подходящую перегрузку метода add, которая бы соответствовала таким типам аргументов.
Crystal также поддерживает универсальные типы:
array = [1, 2, 3] # автоматом становится Array(Int32)
Инференция типов распространяется и на универсальные типы, позволяя компилятору автоматически определять конкретные типы элементов в коллекциях и других структурах данных.
Конкуренция и параллелизм
Модель акторов в Crystal — это абстракция, которая позволяет рассматривать каждую единицу параллельного выполнения как актора, способного обрабатывать сообщения, выполнять задачи и взаимодействовать с другими акторам, все это дает высокий уровень изоляции между акторами
Акторы могут:
-
Создавать других акторов.
-
Отправлять сообщения другим акторам.
-
Обрабатывать входящие сообщения.
В Crystal модель акторов реализована через использование волокон и каналов.
Волокна представляют собой легковесные потоки выполнения, которые позволяют выполнять множество задач параллельно внутри одного операционного потока. В отличие от традиционных потоков, переключение контекста между волокнами происходит быстрее, поскольку оно управляется самим языком, а не ос.
Каналы в Crystal типизированы, что означает, что канал для передачи сообщений определенного типа может передавать только сообщения этого типа.
Создадим несколько волокон для параллельной обработки данных, передаваемых через канал:
channel = Channel(Int32).new # создаем волокна-производители 5.times do |producer_number| spawn do 5.times do |i| value = producer_number * 10 + i puts "Producer #{producer_number} sending: #{value}" channel.send(value) sleep rand(0.1..0.5) # имитация задержки end end end # создаем волокно-потребитель spawn do 25.times do received = channel.receive puts "Consumer received: #{received}" end end sleep 3 # даем время для выполнения волокон
В следующем примере юзаем каналы для сигнализации о завершении асинхронных задач:
done_channel = Channel(Nil).new # асинхронная задача 1 spawn do sleep 1 # имитация длительной операции puts "Task 1 completed" done_channel.send(nil) # отправляем сигнал о завершении end # асинхронная задача 2 spawn do sleep 2 # имитация еще более длительной операции puts "Task 2 completed" done_channel.send(nil) # отправляем сигнал о завершении end 2.times { done_channel.receive } # ожидаем сигналов о завершении обеих задач puts "All tasks completed"
Главное волокно ожидает два сигнала о завершении, прежде чем выводить сообщение о том, что все задачи выполнены.
Полезные библиотеки
Установка библиотек в Crystal осуществляется через систему управления зависимостями под названием Shards. Shards аналогичен Bundler в Ruby, npm в Node.js или pip в Python и используется для управления библиотеками, на которых зависит проект.
Каждый проект на Crystal, использующий внешние зависимости, должен иметь файл конфигурации shard.yml в корневой директории проекта. Этот файлик содержит метаданные проекта и список зависимостей, например:
name: awesome_app version: 0.1.0 dependencies: kemal: github: kemalcr/kemal shards install version: "~> 0.26.1"
После настройки файла shard.yml в терминале юзается команда shards install.
Команда скачает и установит все указанные в файле shard.yml зависимости в папку lib/ проекта. Shards также создаст файл shard.lock, который содержит версии всех установленных зависимостей
Библиотеки импортируются с помощью require.
Kemal
Kemal — это минималистичный веб-фреймворк для Crystal, вдохновленный Sinatra из Ruby. Kemal поддерживает RESTful приложения, предлагая такие функции маршрутизации, шаблоны, поддержку WebSocket.
Например, определение маршрута веб-сокета может выглядеть так:
require "kemal" # маршрут WebSocket ws "/echo" do |socket| socket.on_message do |message| socket.send(message) # эхо-ответ end end Kemal.run
Amber
Amber предлагает MVC архитектуру, ORM, систему шаблонов, веб-сокеты и многое другое, к примеру:
require "amber" class WelcomeController < Amber::Controller::Base def index render("index.ecr") end end Amber::Server.configure do |app| pipeline :web do plug Amber::Pipe::Logger.new plug Amber::Pipe::Session.new end routes :web do get "/", WelcomeController, :index end end Amber::Server.start
Crystal DB
Для работы с бд в Crystal существует несколько библиотек. Основная библиотека crystal-db предоставляет общий интерфейс для работы с различными бдшками. На основе crystal-db построены адаптеры для конкретных СУБД, такие как crystal-mysql, crystal-sqlite3 и crystal-pg для MySQL, SQLite и PostgreSQL соответственно.
Для ORM в Crystal можно использовать библиотеку Granite или Jennifer.
Async
Async позволяет легко создавать асинхронные задачи и управлять ими:
require "async" Async do # асинхронная задача sleep 1 puts "Hello from Async!" end puts "Hello from Main Thread!"
Метапрограммирование
Метапрограммирование позволяет программам генерировать и трансформировать код во время компиляции.
Допустим, есть класс, и нужно автоматически сгенерировать геттеры и сеттеры для его свойств. Это можно сделать с помощью макросов:
class User property name : String property age : Int32 end macro define_properties(properties) {% for property in properties %} @{{property.id}} : {{property.type}} def {{property.id}} @{{property.id}} end def {{property.id}}=(value : {{property.type}}) @{{property.id}} = value end {% end %} end class Person define_properties name: String, age: Int32 end person = Person.new person.name = "Alice" person.age = 30 puts person.name # => Alice puts person.age # => 30
Макрос define_properties генерирует свойства с геттерами и сеттерами для класса Person, используя переданные ему аргументы.
Можно также динамически создавать методы на основе данных, например, массива строк:
class CommandHandler macro register_commands(commands) {% for command in commands %} def handle_{{command.id}} puts "Handling command: {{command.id}}" end {% end %} end register_commands [ "start", "stop", "restart" ] end handler = CommandHandler.new handler.handle_start # => Handling command: start handler.handle_stop # => Handling command: stop handler.handle_restart # => Handling command: restart
Макрос register_commands создает методы handle_start, handle_stop, и handle_restart в классе CommandHandler, используя переданный ему массив команд.
Crystal позволяет использовать аннотации для добавления метаданных к классам, методам и переменным:
annotation Route end @[Route(path: "/users")] class UsersController @[Route(path: "/")] def index # Вывод списка пользователей puts "Список пользователей" end @[Route(path: "/:id")] def show(id : Int32) # Показать пользователя по ID puts "Пользователь #{id}" end end macro generate_routes {% for klass in @type.ancestors %} {% for method in klass.methods %} {% if method.annotation(Route) %} puts "Маршрут: {{method.annotation(Route).path}}, Метод: #{klass}.{{method.name}}" {% end %} {% end %} {% end %} end generate_routes
Здесь определили аннотацию Route, которую затем используем для маркировки методов в классе UsersController с метаданными о пути маршрута. Макрос generate_routes проходит по всем методам, помеченным аннотацией Route, и выводит информацию о маршрутах.
Crystal позволяет использовать макросы для генерации кода на основе типов переменных или параметров.
macro generate_accessor(property, type) def {{property.id}} @{{property.id}} : {{type}} end def {{property.id}}=(value : {{type}}) @{{property.id}} = value end end class User @name : String @age : Int32 generate_accessor name, String generate_accessor age, Int32 end user = User.new user.name = "Alice" user.age = 30 puts user.name # => Alice puts user.age # => 30
Макрос generate_accessor генерирует геттеры и сеттеры для свойств класса User, используя указанные типы.
Макросы в Crystal могут использоваться для динамического создания перечислений:
macro create_enum(name, values) enum {{name.id}} {% for value in values %} {{value.id}} {% end %} end end create_enum Status, [Active, Inactive, Suspended] puts Status::Active # => Active puts Status::Inactive # => Inactive puts Status::Suspended # => Suspended
Макрос create_enum создает перечисление Status с указанными значениями.
Растущее сообщество Crystal постоянно работает над улучшением языка, добавлением новых библиотек и фреймворков.
Больше про языки программирования рассказывают эксперты OTUS в рамках практических онлайн-курсов. С полным каталогом курсов можно ознакомиться по ссылке.
ссылка на оригинал статьи https://habr.com/ru/articles/794983/
Добавить комментарий