Статьи

Clojure для чайников: ката

Clojure — это диалект LISP ; Если вы когда-либо были вынуждены использовать LISP на уроках информатики, я понимаю ваше сопротивление этому классу полностью функциональных языков.

Фактически, LISP для многих из нас означает много обратной польской нотации, где (+ 1 2) оценивается как три; отсутствие циклов и других товаров для привилегированной хвостовой рекурсии; и абсолютное отсутствие состояния в виде переменных (состояние на самом деле отсутствует: я видел его в стеке).

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

Известный факт о Clojure заключается в том, что он может компилироваться в байт-код JVM или CLR , что означает, что он может быть развернут на тех же платформах, что и приложения Java или C #. Это определенно нужно учитывать, и, как и для Scala, это положительно влияет на принятие языка.

Начиная?

Загрузите и разархивируйте
последний выпуск Clojure . Запустите класс, указав clojure-1.3.0.jar в classpath:

java -cp clojure-1.3.0.jar clojure.main

(проверьте версию: на момент написания этой статьи последний стабильный выпуск был 1.3.0.) Вы можете играть с циклом Read-Eval-Print-Loop, как вы могли бы делать с Python или Ruby:

[15:45:20][giorgio@Desmond:~/clojure-1.3.0]$ java -cp clojure-1.3.0.jar clojure.main
Clojure 1.3.0
user=> (+ 2 3)
5
user=>

Однако добавьте это в ваш .bashrc для удобства:

alias clj="java -jar ~/clojure-1.3.0/clojure-1.3.0.jar"

Теперь вы можете набрать
clj file.clj для выполнения кода Clojure.

Введение в язык?

Я уже немного знал LISP, поэтому пропустил введение. Существует длинный учебник, который поможет вам полностью перейти от императивного стиля, если вы еще не сталкивались с диалектом LISP, таким как Scheme или Common LISP. Есть также несколько примеров , посвященных стилю Scala и Ruby Koan, но без комментариев дзен. Они запускаются в браузере, поэтому могут показаться неестественными.

Таким образом, как можно исследовать Clojure, пропуская скучные введения? С простым ката, самостоятельным программированием : я предпочитаю заниматься вместо чтения. Ката FizzBuzz — это мой стандартный способ определения, является ли человек разработчиком, и изучение нового языка или среды . Это похоже на изменение одного параметра эксперимента, такого как температура, вес или концентрация, чтобы ясно понять, что происходит, не путая переменные в пути.

Однако для выполнения ката мне нужен способ проверить мой код. К счастью, с Clojure поставляется среда тестирования .

тестирование

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

(ns fizzbuzztests
    (:use clojure.test))
(deftest test-adder
    (is (= 24 (+ 2 22))))
(deftest test-adder
    (is (= 24 (+ 2 23))))
(run-tests 'fizzbuzztests)

Выполнение этого кода покажет неудачный тест, конечно:

Testing fizzbuzztests

FAIL in (test-adder) (fizzbuzztest.clojure:6)
expected: (= 24 (+ 2 23))
  actual: (not (= 24 25))

Ran 1 tests containing 1 assertions.
1 failures, 0 errors.

FizzBuzz ката

Я выполнил ката, добавив один тест за один раз , и много гуглял между провальным тестом (легко написать) и его реализацией и последующим рефакторингом. Мне пришлось искать, как определить функцию, где был оператор целочисленного модуля, выражение let для рефакторинга, конкатенация строк … Но я думаю, что я изучил довольно много базовых функций.

Целью, которую я поставил, было написание функции (fizzbuzz x) . Конечно, код, который я получаю, ужасен и полон ifs (они тоже плохи в функциональном программировании, верно?) Но он работает, он выразителен и является отправной точкой для изучения больше о Clojure.

Осторожно, спойлеры

Вот окончательный результат по сравнению с императивным языком. Имейте в виду, что я всегда использовал императивный язык, такой как Java, PHP или JavaScript, поэтому на этот ката может повлиять мой предыдущий опыт (и я только что обнаружил сокращение defn ).

Кстати, если вы думаете, что JavaScript сильно отличается от Java, то сейчас самое время попробовать Clojure или другие LISP. Вот версия JavaScript:

function FizzBuzz(correspondences) {
    this.correspondences = correspondences;
    this.accept = function (number) {
        var result = '';
        for (var divisor in this.correspondences) {
            if (number % divisor == 0) {
                result = result + this.correspondences[divisor];
            }
        }
        if (result) {
            return result;
        } else {
            return number;
        }
    }
}

А вот Clojure , в комплекте с тестами:

(ns fizzbuzz
  (:use clojure.test))
(def divides (fn [x divisor]
    (= 0 (mod x divisor))))
(def nameNumbers (fn [x mappingsOfNumbers]
    (if (empty? mappingsOfNumbers)
        ""
        (let [divisor (key (first mappingsOfNumbers))
              value (val (first mappingsOfNumbers))]
            (if (divides x divisor)
                (str value (nameNumbers x (rest mappingsOfNumbers)))
                (nameNumbers x (rest mappingsOfNumbers)))))))
(def fizzbuzz (fn [x]
    (let [names (nameNumbers x {3 "Fizz" 5 "Buzz"})]
        (if (= "" names)
            x
            names))))
(deftest test1ShouldBeLeftUntouched
    (is (= 1 (fizzbuzz 1))))
(deftest test2ShouldBeLeftUntouched
    (is (= 2 (fizzbuzz 2))))
(deftest test3isFizz
    (is (= "Fizz" (fizzbuzz 3))))
(deftest test5isBuzz
    (is (= "Buzz" (fizzbuzz 5))))
(deftest test6isFizz
    (is (= "Fizz" (fizzbuzz 6))))
(deftest test10isBuzz
    (is (= "Buzz" (fizzbuzz 10))))
(deftest test15isFizzBuzz
    (is (= "FizzBuzz" (fizzbuzz 15))))
(run-tests)