Я заметил ошибку в нашем продукте (Rails), из-за которой изображения иногда сохраняются как URL-адреса данных в текстовых блобах в нашей базе данных. Мне было интересно, сколько людей пострадало от этого и сколько места заняли эти дополнительные данные. Но довольно скучно вычислять это на Ruby, C# или чем-то еще. Поэтому я решил попробовать его с Clojure. Я впервые пишу на Clojure. Вот как это было.

Подготовка

Настроить (на моем Mac) было очень просто:

brew install leiningen

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

Оставайтесь сильными и замените

Запуск lein repl запускает изящный маленький инструмент командной строки для оценки Clojure. Но в итоге я предпочел использовать repl, встроенный прямо в Nightcode (вышеупомянутый редактор). Я взрывал его несколько раз. Это удобная функция, но если вы имеете дело с большими объемами данных, возможно, вы захотите действовать осторожно.

По сути, мой процесс разработки Clojure был таким:

  • Разбейте проблему на небольшой функциональный рецепт
  • Используйте repl, чтобы выяснить, как кодировать функцию
  • Закодируйте функцию
  • Используйте ответ для проверки функции
  • Переходите к следующей функции в рецепте… полоскание, повтор.

Управление проектом.clj

В любом существенном проекте (даже в этом маленьком) вы должны иметь возможность добавлять зависимости. К сожалению, в Leiningen нет эквивалента npm install --save ‹packagename›. Поэтому поиск правильной (последней) версии для каждого пакета довольно болезненный.

Пришлось хорошенько погуглить. Одни располагались на одном сайте, другие на GitHub, третьи на случайных блогах. Я чувствую, что должен что-то упустить, потому что я не могу представить, чтобы процесс поиска пакетов Clojure был таким ручным.

Во всяком случае, мой project.clj в итоге выглядел так:

(defproject stats "0.0.1-SNAPSHOT"
  :description "Whatevz"
  :dependencies [[org.clojure/clojure "1.8.0"]
                 [org.clojure/java.jdbc "0.6.2-alpha3"]
                 [mysql/mysql-connector-java "6.0.3"]]
  :javac-options ["-target" "1.6" "-source" "1.6" "-Xlint:-options"]
  :aot [stats.core]
  :main stats.core)

Nulls, substr и т. д.

Я не занимался функциональным программированием, но Haskell и F# заставили меня забыть о существовании null. Итак, мой Clojure несколько раз взрывался с ошибками нулевого указателя. Кроме того, функция подстроки в Clojure взрывается, если вы ошиблись с границами, поэтому мне пришлось написать trunc.

;; Make sure we have a non-nil string
(defn safe-str [s] (if (nil? s) "" s))
;; Truncate a string safely
(defn trunc
  [str n]
  (let [s (safe-str str)]
    (subs s 0 (min (count s) n))))

Недостаточно памяти при обработке больших таблиц базы данных

Оказывается, запрос org.clojure/java.jdbc по умолчанию не является ленивым. Думаю, это имеет смысл, так как соединение с базой данных должно оставаться открытым неопределенное время.

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

Что ж, есть довольно удобный способ ленивой обработки записей: result-set-fn, как описано здесь.

;; Load up activities and crunch numbers and such
;; This passes a lazy sequence of hashes/maps into the compute-stats 
;; function. Whatever compute-stats returns is also returned by j/query.
(defn get-activity-stats []
  (j/query db
    ["select description from activities"] 
    {:result-set-fn compute-stats}))

Функция Compute-Stats — это функция, которую я определил в другом месте. Он может просто рассматривать свой аргумент как любой старый список карт. Хороший.

Нет статической типизации

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

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

Когда вы возились с F# или Elm, вы, как правило, забываете, что некоторые функциональные языки имеют ошибки времени выполнения. (В F# и Elm они тоже случаются, но гораздо реже).

Скобка

Скобки в некоторых случаях затрудняют чтение кода: «Какая из этих 7 скобок является концом оператора if?» Но я думаю, это просто потому, что я к ним не привык и потому что я не научился доверять Nightcode.

Ночной код окрашивает каждый набор скобок, облегчая их сопоставление. Он также делает какое-то сумасшедшее интеллектуальное автоматическое закрытие ваших скобок. Это здорово, на самом деле. Я думаю, что все мои проблемы со скобками на самом деле не являются проблемами, когда вы просто вводите то, что хотите, и позволяете Nightcode магически расставить скобки на место.

Минусы

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

  • Нули
  • Кажется, в Лейнингене нет возможности установить — сохранить вариант
  • Время запуска довольно медленное (я буду винить JVM)
  • Статическая типизация — это гражданин 2-го сорта
  • Нет автоматического каррирования
  • JVM

Я прикрепил туда последний, просто потому что могу. Это, очевидно, тоже профессионал, в зависимости от того, как вы на это смотрите.

Вывод

Могу ли я рекомендовать Clojure? Ага. Я буду.

Это язык Lispy, который хорошо спроектирован и на самом деле пользуется спросом (извините, PicoLisp и Chiken Scheme).

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

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

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

Для больших проектов я все же склоняюсь к языку со статической типизацией. В нашей кодовой базе Rails/JavaScript меня слишком много раз кусали глупые проблемы, которых никогда не было, когда я писал C#. Динамическая природа Clojure оставляет его открытым для точно такого же набора проблем.

Если бы у F# было такое живое и сильное сообщество, как у Clojure, я бы безоговорочно выбрал этот язык. Но это не так. Так что это не так. Двигаясь вперед, я собираюсь больше возиться с Clojure. Это просто может стать моим языком выбора.