В компании Duo много лет, в качестве основной платформы, использовали Node. Однако, в последнее время они экспериментировали с очень новым, ещё не вполне оформившимся языком Crystal. По их словам, чем больше они им занимались — тем сильнее к нему привязывались.

Сегодня мы хотим поделиться с вами переводом их рассказа о сильных и слабых сторонах платформ Node и Crystal, и о том, почему в Duo всё больше серверных проектов переводится на Crystal.



Node


?Ожидания


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

Node тогда показался нам совершенно очевидным выбором. Если мы брали в штат разработчика, который знал JavaScript, это означало для нас, что он смог бы работать и на клиентских, и на серверных проектах. Инструменты, синтаксис и зависимости перекрывались бы, и все совершенствовали бы свои навыки, так как любая задача подразумевала бы использование JavaScript.

?Реальность


У серверного и клиентского кода совершенно разные цели, эти виды кода требуют знания очень разных приёмов работы. Обычно клиентский код — это взаимодействие с пользователем, обновление интерфейса, выполнение запросов данных с сервера. Наши разработчики обычно работали с webpack или browserify для упаковки кода, разрабатывали интерфейсы на React и использовали CSS-фреймворки для упрощения разметки страниц.

На сервере программист имеет дело с SQL-запросами к базам данных, с ORM, с чтением и записью файлов, организует взаимодействие со сторонними API. Потоки данных на сервере подчиняются модели «запрос — ответ». Между запросами все задачи должны обслуживать ответы, и всё это нужно делать специфическим образом. Если некий шаг полагается на результаты, полученные на предыдущем шаге, соответствующие процессы должны выполняться по порядку, если нет — их можно выполнять параллельно.

?Стандартная асинхронность


Node спроектирован так, что каждую задачу он выполняет асинхронно. Это означает, что если вы предложите Node выполнить 5 задач, он попытается сделать всё это одновременно. Несколько последних лет основным средством для поддержки такой модели работы были промисы. Если в двух словах, то промисы позволяют программисту объединять в цепочки наборы асинхронных задач, которые представляют собой последовательности шагов, выполняемых друг за другом.

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

За годы использования Node создание цепочек из промисов стало для нас нормой. Половина написанного кода была направлена на то, чтобы превратить асинхронные задачи в задачи, решаемые последовательно. Эти цепочки промисов тяжело тестировать, отлаживать, код получается не особенно вразумительным. Часто бывает так, что просто глядя на код тяжело понять даже то, в каком порядке выполняются задачи и подзадачи.

Роаль Даль, создатель Node, весьма удачно описал эту ситуацию, сравнивая Node и Go в интервью:

Но интерфейс, который эта система предоставляет программисту, блокирующий, и я думаю, на самом деле, что это — более удачная модель программирования. Использование блокирующего подхода позволяет, во многих ситуациях, гораздо лучше видеть суть выполняемых действий. Скажем, если есть куча последовательных действий, весьма полезна возможность сообщить системе примерно следующее: «Реши задачу А, подожди ответа, возможно — выдай ошибку. Реши задачу B, подожди ответа или выдай ошибку». И в Node, из-за необходимости постоянно «прыгать» между вызовами функций, достичь такого гораздо сложнее.

?Динамические типы


Любой, кто регулярно программирует на JavaScript, рано или поздно познакомится с ошибкой «undefined is not an object». Эта ошибка возникает, когда вы пытаетесь обратиться к методу или свойству переменной, которую вы считаете объектом, но в которую записано значение null. Недостаточно контролировать то, какие данные передаются между асинхронными участками кода, необходимо ещё и быть в курсе того, что творится с типами в любом месте кода приложения. Каждый раз, когда приложение получает данные от одного процесса и передаёт их другому, может произойти сбой. Если вы не предусматриваете возможность обработки всех возможных значений, сервер выдаст ошибку, или, что гораздо хуже, сделает что-нибудь неожиданное.

Crystal


Во время работы с Node я исследовал множество других языков и платформ, в том числе — Python, PHP, Ruby и Go. Они, как правило, либо были медленнее, чем Node, либо не так удобны для целей разработки. Скорость и синтаксис — это две вещи в языке, которые можно оптимизировать лишь до определённого предела.

Затем, в прошлом году, я прочитал статью о языке Crystal. Он — из нового поколения языков, которые компилируются в машинный код через LLVM. Его синтаксис похож на Ruby (мне это нравится), но работает он так же быстро, как Go (а этому зверю скорости не занимать!).

Crystal всё ещё очень молод, но я решил переделать на нём некоторые серверные части нашей системы управления контентом. Получилось просто замечательно. Вот, что я выяснил в ходе работы:

  • Crystal отличается высокой производительностью. Для моих задач он оказался в 2 раза быстрее Node.
  • Он использует очень мало памяти. Crystal обычно надо менее чем 5 Мб на процесс, а Node — более 200 Мб.
  • У него имеется отличная стандартная библиотека, в результате для решения типичной задачи нам понадобилось лишь 12 зависимостей, в сравнении с сотней зависимостей Node.
  • Код, по умолчанию, выглядит синхронным, он использует, как и Node, цикл событий, но для организации параллелизма применяются легковесные потоки (fibers), взаимодействие организовано через каналы, как у Go. Это упрощает понимание кода.
  • Crystal статически типизирован, поэтому об ошибках можно узнать при компиляции.
  • Crystal выводит типы, в результате, его системой типов легко пользоваться, так как не приходится слишком часто использовать аннотации типов.

Мне так понравилось программировать на Crystal, что мы переписали весь бэкенд нашей CMS на этом языке. Его API совместимо с нашей CMS, основанной на Node, в результате вебсайты можно перевести на новую систему, или вернуть обратно на старую, затратив сравнительно мало усилий. Это важно, так как Crystal — всё ещё молодой проект и нам нужна страховка.

После того, как наша DuoCMS была полностью переписана на Crystal, мне понадобилось протестировать её в продакшне. Собственно говоря, оригинал этого материала размещён на сайте, который работает на Crystal.

?Сравнение кода на Node и Crystal


Ниже, для сравнения, приведена слегка упрощённая версия кода контроллера, написанного на Crystal и Node.

Вот контроллер на Node (используется фреймворк Express).

const express = require('express')
const app = express()
const bodyParser = require('body-parser')
const UserService = require('user-service')
app.use(bodyParser.json())

app.get('/', function (req, res) {
  res.send('Hello World!')
})

app.post('/api/users', function (req, res) {
  if(request.body){
    UserService.save(request.body)
    .then(function(){
      res.send('user saved')
    })
    .catch(function(err){
      res.send(err)
    })
  }else{
    res.send("no user provided")
  }	
})

app.listen(3000, function () {
  console.log('Example app listening on port 3000!')
})

Вот — контроллер на Crystal (используется фреймворк Kemal).

require "kemal"
require "user"
require "user-service"

get "/" do
  "Hello World!"
end

post "/api/users" do |ctx|
  if (json = ctx.request.body)
    user = User.from_json(json)
    UserService.new.save(user)
    "user saved"
  else
    "no user provided"
  end
end

Kemal.run

Несложно заметить, что структура этих двух примеров очень похожа. Однако, когда отпала необходимость в промисах, общий объем кода уменьшился. При написании более крупных приложений это заметно ещё сильнее. Серверный код DuoCMS 5 состоит из примерно 15609 строк на JavaScript. Объём кода DuoCMS 6 близок к 10186 строкам. На данный момент DuoCMS 6 имеет больше возможностей, для реализации которых потребовалось на 30% меньше кода. При этом, благодаря отсутствию промисов, этот код гораздо легче читать и поддерживать.

Чего не хватает в Crystal?


Разработчики называют текущий релиз Crystal альфа-версией. Тут надо сказать, что мне приходилось использовать гораздо менее проработанные фреймворки, предназначенные для продакшна. В худшем случае я сказал бы, что Crystal сейчас в состоянии бета-версии. Однако, я могу понять осторожность разработчиков. Они говорят об альфа-версии, так как это даёт им пространство для манёвра, для внесения изменений, даже для того, чтобы поломать какое-нибудь API, и так далее.

Я использую Crystal уже примерно год и столкнулся лишь с немногими изменениями, которые объясняются развитием проекта и тем, что это — всё ещё альф-версия. У меня было больше проблем с обновлением React на фронтенде. Кроме того, стоит сказать, что Crystal написан на Crystal, то есть, если что-то окажется нерабочим, вы можете вносить исправления в язык и в стандартную библиотеку (я так и поступал).

На данный момент основными недостающими возможностями Crystal можно назвать следующие:

  • Всё ещё нет поддержки Windows (меня это не беспокоит, работаю я на Mac, код разворачиваю на Linux).
  • До сих пор нет настоящего параллелизма (в Node его тоже нет).
  • Нет инкрементной компиляции (это было бы очень удобно, так как сейчас, для компиляции нашей системы после внесения изменений в код, требуется около 8 секунд).
  • Существует не так много хорошо поддерживаемых опенсорсных библиотек для Crystal, но здесь всё придёт в порядок, когда начнётся использование Crystal в серьёзных проектах.

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

Итоги


Стоит ли вам попробовать Crystal? Да, стоит! Штука это действительно замечательная. На Crystal приятно программировать, код просто читать и править. И, кстати, чем больше людей будет пользоваться Crystal и вносить вклад в разработку этого языка — тем лучше он будет становиться. Хотите увидеть всё своими глазами? Вот инструкции по установке. Вот — сайт проекта. А это — чат Crystal, если что — пишите мне на @crisward.

Если вы спросите — следует ли вам использовать Crystal в продакшне, ну — это как хотите. Лично я думаю, что единственный способ сделать что-либо пригодным к практической эксплуатации — попробовать это на тех задачах, на которых допустимы сбои, а потом постепенно переходить на это в более масштабных проектах. Мы не используем Crystal везде, например — на проектах с очень высоким трафиком, или на критически важных участках. Мы мониторим все наши сайты и регулярно их бэкапим, кроме того, Node всегда на подхвате — на тот случай, если с Crystal вдруг что-нибудь приключится.

Уважаемые читатели! Планируете ли вы попробовать Crystal в своих проектах?

Комментарии (47)


  1. alek_sys
    15.09.2017 16:03
    +15

    Эта статья будет не полной без самого популярного комментария из обсуждения на Reddit:


    Let me sum up.
    "We're JS twinks with no real experience with backend software dev, so instead of learning a new language that's well proven on the backend like Ruby or Python, we picked a JavaScript tool with almost no track record as a backend platform. Then we realized it sucks for a huge number of backend applications! So naturally…
    1) we picked yet another completely unproven, alpha quality platform… 2) that nobody else uses in production, so support is hard to come by when it breaks,… 3) with a custom syntax (even though we picked node instead of a good platform specifically to avoid learning a new syntax)… 4) AND WE REWROTE ALL OUR SHIT BECAUSE YOLO"
    Whoever makes tech decisions for this team is an irresponsible, incompetent idiot who has no business being near production systems.


    1. justboris
      15.09.2017 18:04

      Кому-то же нужно пробовать новые технологии. Очень хорошо, что кто-то потратил свое время и деньги, а не наше.


    1. akzhan
      15.09.2017 20:15

      так всегда начинается :-) у меня один продакшн-сервис для работы с кредитными картами написан на Crystal, но он однозначно недоступен снаружи :)


  1. beduin01
    15.09.2017 16:51

    Я бы советовал на D обратить внимание в сочетании с vibed.org


    1. vintage
      16.09.2017 17:02
      +1

      Присоединяюсь.


      Crystal отличается высокой производительностью. Для моих задач он оказался в 2 раза быстрее Node.

      Да что угодно компилируемое быстрее ноды :-)


      Он использует очень мало памяти. Crystal обычно надо менее чем 5 Мб на процесс, а Node — более 200 Мб.

      Ну это как приложение напишешь.


      У него имеется отличная стандартная библиотека, в результате для решения типичной задачи нам понадобилось лишь 12 зависимостей, в сравнении с сотней зависимостей Node.

      На D возможно хватило бы и одной — vibe.d


      Код, по умолчанию, выглядит синхронным, он использует, как и Node, цикл событий, но для организации параллелизма применяются легковесные потоки (fibers), взаимодействие организовано через каналы, как у Go. Это упрощает понимание кода.

      В D аналогично, более того..


      До сих пор нет настоящего параллелизма (в Node его тоже нет).

      В D он мало того, что есть, так ещё некоторым контролем времени компиляции. Файберы могут баланситься на воркеры. Есть и реализация каналов как в го.


      Crystal статически типизирован, поэтому об ошибках можно узнать при компиляции.

      D тоже, кроме того имеет мощные возможности метапрограммирования. Например, шаблоны могут транслироваться в машинные коды при компиляции.


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

      D тоже их выводит, но более строго. Например, не пихает алгебраические типы где ни попадя, как ниже в комментах.


      Всё ещё нет поддержки Windows (меня это не беспокоит, работаю я на Mac, код разворачиваю на Linux).

      А меня беспокоит, ибо работаю под виндой. И D работает и под линуксом и под виндой. И тоже умеет компилироваться через LLVM.


      Нет инкрементной компиляции (это было бы очень удобно, так как сейчас, для компиляции нашей системы после внесения изменений в код, требуется около 8 секунд).

      А в D она есть, из-за чего компиляция идёт очень быстро. Настолько быстро, что на нём даже скрипты писать можно с компиляцией налету.


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

      D уже довольно зрелый, с богатым репозиторием библиотек и своим пакетным менеджером.


      1. Riim
        17.09.2017 17:45

        Для D есть аналог Passport.js? Пока удалось найти только https://github.com/thaven/oauth, но хотелось бы стабильную версию, больше готовых провайдеров и хоть какую-то активность в репозитории.


        1. lega
          17.09.2017 18:27

          Passport.js можно использовать как микросервис, без разницы на чем остальной бекенд.


          1. Riim
            17.09.2017 18:38

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


        1. vintage
          17.09.2017 21:02

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


  1. NoPluses
    15.09.2017 16:54
    -3

    Зачем переходить именно на Ruby Cristal, если есть много других более развитых платформ бэкэнда. Те же рельсы или Go.
    И чем вам Node не нравится
    Фактически на одном языке и фронтэнд и бэкэнд


    1. vbif
      15.09.2017 18:35
      +1

      Написали же, именно это авторам оказалось не нужно.


  1. Leg3nd
    15.09.2017 16:57
    +3

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


    async/await через babel еще 2 года назад уже отлично работали


  1. Crandel
    15.09.2017 17:09

    На Rust тоже можно писать веб приложения :)


  1. Dreyk
    15.09.2017 17:14

    мне Crystal нравится (потому что я рубист), но вся проблема в том, что язык еще не релизнулся: могут быть баги, обратно-несовместимые изменения и прочие прелести


    1. akzhan
      15.09.2017 20:15

      гарантированно будут (я надеюсь).


    1. valentinmk
      15.09.2017 20:20

      А если взять какую-то ruby библиотеку и тупо компильнуть кристалом — взлетит? Или там такой игривый руби-лайк синтаксис, сейчас я руби, а сейчас нет? (я не рубист, если что)


      1. akzhan
        15.09.2017 21:01
        +2

        в 98% случаях не взлетит, потребует хотя бы рихтовки напильником.


        Crystal просто основан на некоторых идеях из Ruby.


        Но вообще это статически-типизируемый язык с type unions, редкая штука.


        То есть тип Int32 — не может быть nilable, а Int32 | Nil может (другая запись — Int32?).


        И это очень сильно и удобно (и да, можно Int64 | String, например, но вообще типы он сам выведет при компиляции, если явно не писать).


        1. Tiberiumk
          15.09.2017 23:23

          Int64 | String — это же обычные дженерики, нет?


          1. akzhan
            16.09.2017 05:26

            Нет, метод может вернуть type union Int64 | String.


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


            def iors : Int64 | String
              Math.rand > 0.5 ? 5 : "s"
            end
            
            r = iors
            if r.is_a?(String)
              puts "wow #{r}"
            end

            здесь для разных типов будут созданы/скомпилированы разные ветви исполнения, так что потеря времени компиляции только на определение типа возвращаемого значения (одна проверка только один раз после получения результата, если это необходимо)


            1. akzhan
              16.09.2017 05:31

              более корректно код писать как:


              def iors
                Random.rand > 0.5 ? 5 : "s"
              end
              
              r = iors
              if r.is_a?(String)
                puts "wow #{r}"
              end


          1. buldo
            18.09.2017 13:01

            Наверное тут имеются в виду алгебраические типы данных. То есть, например, метод может вернуть или int32 или string, а потом в зависимости от того, что по факту вернулось (с помощью if или какого pattern matching) будет своя ветка исполнения.
            Ещё один пример — функция возщает или ответ или ошибку


        1. valentinmk
          16.09.2017 10:57

          Слушай (это гипотетическая идея только из теоритического интереса), а ведь можно изобрести новый колбэк хелл.
          Из примера ниже


          def iors : Int64 | (puts "Welcome to hell" > Nil ? String : Nil)
              Math.rand > 0.5 ? 5 : "s"
          end


  1. akeinhell
    15.09.2017 17:19
    +4

    Несложно заметить, что структура этих двух примеров очень похожа. Однако, когда отпала необходимость в промисах, общий объем кода уменьшился. При написании более крупных приложений это заметно ещё сильнее. Серверный код DuoCMS 5 состоит из примерно 15609 строк на JavaScript. Объём кода DuoCMS 6 близок к 10186 строкам. На данный момент DuoCMS 6 имеет больше возможностей, для реализации которых потребовалось на 30% меньше кода. При этом, благодаря отсутствию промисов, этот код гораздо легче читать и поддерживать.


    Можно и порефакторить этот js и получим тоже — читаемый код и уменьшение кодовой базы
    const router  = require('express').Router();
    
    app.get('/', (req, res) => res.send('Hello World!'));
    
    app.post('/api/users', async (req, res) => {
      const {body} = request;
      if (!body) {
        return res.send("no user provided")
      }
      
      await UserService.save(request.body);
      res.send('user saved');
    })


    1. Leopotam
      15.09.2017 20:12
      +2

      К тому же на crystal нет обработки ошибки сохранения пользователя — замечательное сравнение.


      1. akaluth
        16.09.2017 10:02

        Скорее всего, обработка исключений происходит внутри фреймворка (как в sinatra), так что всё ок


        1. Leopotam
          16.09.2017 10:45

          Так какая разница, где оно обрабатывается? Как я узнаю, что произошла нештатная ситуация? Наружу ничего не передается и, судя по всему, даже не кидается исключение.


          1. akaluth
            16.09.2017 20:45

            Исключение может кидаться внутри сервиса, создающего юзеров или в модели. Так как js-версия ничего не делает, кроме как выводит ошибку в браузер, код примерно равнозначен: тут это исключение обработает фреймворк, отдав корректный 500-ый статус (чего, кстати, не сделает js-вариант)


    1. akzhan
      15.09.2017 21:17
      +1

      Ну на деле Kemal, — это просто первая ласточка, сейчас есть и иные каркасы, типа https://ambercr.io


      Во-вторых, да, async/await — прекрасная концепция.


      А в остальном, — Crystal надежнее и удобнее в силу своей статической типизации, тут его ближайший конкурент все-таки — tsnode Golang.


  1. vladfaust
    15.09.2017 17:57

    Я — активный фанат кристала и мне больно смотреть, как в него приходят js'еры и рубисты и начинают буквально ср*ть в опенсорс, ни разу не используя преимущества языка. Они думают, что они всё ещё в динамическом ЯП.


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


    А ещё автор забыл упомянуть макросы. Метапрограммирование есть и в кристале, и это одна из самых главных его особенностей.


    1. rubyrabbit
      16.09.2017 09:52
      +2

      Напишите статью )


    1. Leopotam
      16.09.2017 10:46
      +2

      А почему тогда не nim? Явно быстрее crystal, есть то же самое метапрограммирование через макросы и прочее.


    1. Druu
      16.09.2017 11:17

      > А ещё автор забыл упомянуть макросы. Метапрограммирование есть и в кристале, и это одна из самых главных его особенностей.

      Макросы на строках, без гигиены, в негомоиконном языке — это не метапрограммирование, это курам на смех.


      1. akzhan
        17.09.2017 20:21

        Ну как раз в Crystal макросы — на AST.


        Без гигиены — писать комментарии, не прочитав о самом объекте обсуждения.


        1. Druu
          18.09.2017 03:02

          > Ну как раз в Crystal макросы — на AST.

          В презентации сказано, что АСТ — только промежуточное представление и в итоге из него генерятся строки.


          1. akzhan
            18.09.2017 11:03

            Строки, полученные при раскрытии макроса, далее преобразуются в AST, и продолжается уже разбор подставленного поддерева.


            1. Druu
              18.09.2017 12:52

              И толку, если там в середине были строки? Нормальную макросистему на этом изобразить нельзя, в строках просто нету требуемой информации.


  1. raveclassic
    15.09.2017 18:10

    Если вам по душе ruby-like синтаксис, то почему тогда не Elixir?


    Блин, перевод же


  1. Fen1kz
    15.09.2017 19:12
    +1

    Я думаю проблема в том что автор пытался на ноде кодить синхронно.


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


    class UserController {
      requests(router) { // router уже держит /api 
        router.post('/users', this.handleRequest(this.addUser))
      }
    
      addUser(req) {
        // вообще у нас прикручен валидатор на запросы, но ладно уж
        if (!req.body) throw new ServerError("no user provided"); // addUser исполняется в промисе, так что ошибка вывалится в catch
    
        return UserService.save(req.body)
            .then(() => 'user saved');
      }
    }


  1. SirEdvin
    15.09.2017 21:33

    Они, как правило, либо были медленнее, чем Node, либо не так удобны для целей разработки.

    Интересно, python медленее nodejs (если все-таки использовать асинхронность) или менее удобен?)


    А так crystal наконец-то похож на язык, который как python по гибкости, только компилируется. Надо будет глянуть.


    1. akzhan
      15.09.2017 22:16

      пока желательно только для инфраструктуры внутренней.


      наружу раньше февраля не стоит.


      1. Extrapolator
        16.09.2017 12:18

        а что планируется в феврале?


        1. akzhan
          16.09.2017 12:29
          +1

          Жесткого плана нет (есть карта).


          Ранее была амбициозная цель к 2018 году создать Crystal 1.0, готовый к промышленной эксплуатации.


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


          Мне вот не хватает только иногопоточности для некоторых применений.


  1. ReklatsMasters
    16.09.2017 00:48
    +2

    Мне так понравилось программировать на Crystal, что мы переписали весь бэкенд нашей CMS на этом языке.

    Я правильно понял, что автор только-только изучил новый язык и сразу принялся на нём писать в прод? Крайне сомнительная и не профессиональная затея. Возможно, стоило отрефакторить старый код, убрать лишние зависимости. Уверен, это дало бы прирост CPU / памяти.


  1. unabl4
    16.09.2017 01:50
    +1

    Я как рубист на кристал давно засматриваюсь. Попробовать очень хочу, но пока никак.


  1. lega
    16.09.2017 12:05
    -1

    Crystal
    Почему не Nim или Julia?


    1. beduin01
      16.09.2017 21:15

      Потому что Nim очень сырой и в нем хаотично надерганы концепции из кучи других языков.
      Julia все же больше для математиков и ученых создана.


  1. Senyaak
    18.09.2017 13:02
    +1

    называйте меня как хотите, но сравнивать языки со скобками и языки с «end» это…