Я заметил ошибку в нашем продукте (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. Это просто может стать моим языком выбора.