Статьи

Мои ноги промокли от Clojure

Я начал жесткое кодирование в Project Plugh . Я работаю над переносом нескольких концепций Lift на Clojure, когда создаю кометные возможности Lift в Clojure, чтобы я мог передавать данные в браузер.

Фон … PartialFunction

В Scala есть PartialFunction

Ключевым понятием для PartialFunctions является «… это унарная функция, в которой домен не обязательно включает все значения типа A.»

Очень полезна возможность протестировать функцию PartialFunction чтобы увидеть, содержит ли домен определенное значение. pf.isDefinedAt(x) позволяет проверить, определена ли функция с заданным значением x .

Но PartialFunction является подклассом Function , поэтому можно применять PartialFunctions :

1
pf(x)

Компилятор Scala возьмет шаблон и превратит его в PartialFunction :

1
2
3
4
5
6
7
def pf: PartialFunction[String, Number] =
{
  case "" => 0 // special case blank to zero
  case x if isInt(x) => x.toInt
  case x if isDouble(x) => x.toDouble
  case x if isBigInt(x) => asBigInt(x)
}

Другое свойство PartialFunction — они могут быть составлены:

1
2
pf = pf1 orElse pf2 orElse pf3 // pf isDefinedAt any place
                               // any of the partial functions are defined

Мы широко используем PartialFunctions в Lift, чтобы позволить выбрать, должен ли Lift обслуживать конкретный URL, должен ли он обслуживаться определенным обработчиком REST и т. Д. Например, определяя маршрут REST в Lift:

1
2
3
4
serve {
  case "api" :: "user" :: AsLong(userId) :: _ GetJson _ =>
       User.find(userId).map(_.toJson)
}

Поскольку я изучал Clojure в рамках подготовки к презентации в Strange Loop и как часть нового проекта, над которым я работал , я стремлюсь привнести лучшие вещи в Lift в код Clojure, который я пишу.

В Clojure

Материал соответствия Clojure довольно изящен. Мне особенно нравится, как вы можете извлекать значения из карты (это намного мощнее, чем сопоставление с образцом в Scala, даже при неприменении … но я отвлекся).

Итак, я написал макрос:

1
(defmacro match-func [& body] `(fn [~'x] (match [~'x] ~@body)))

Это создает функцию, которая является применением сопоставления с параметром, поэтому:

1
2
((match-func [q :guard even?] (+ 1 q) [z] (* 7 z)) 33)
;; 231

Оказывается, шаблонизатор Clojure будет извлекать значения в несвязанные переменные. Но связанные переменные проверяются … это означает, что:

1
((match-func [[x y]] (+ x y)) [4 5])

Оказывается, это проблема, потому что x связан в макросе match-func … поэтому нам нужно заменить x на что-то другое. Итак, мы должны изменить переменную x на что-то другое:

1
2
3
(defmacro match-func [& body]
    "Create a function that does pattern matching."
    `(fn [x#] (match [x#] ~@body)))

isDefinedAt

Итак, как мы можем проверить, соответствует ли шаблон определенному значению?

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

Ответ арности. Scala имеет функции с определенной арностью. Оказывается, что Clojure может иметь единственную функцию, которая ведет себя по-разному в зависимости от арности вызова. Ура!

Итак, макрос выглядит так:

01
02
03
04
05
06
07
08
09
10
(defmacro match-pfunc [& body]
  "Create a partial function that does pattern matching."
  (let [rewrite (mapcat (fn [x] [(first x) true]) (partition 2 body))]
  `(fn ([x#] (match [x#] ~@body))
       ([x# y#]
         (cond
           (= :defined? x#)
           (match [y#] ~@rewrite)
           (= :body x#)
           '(~@body))))))

Это дает нам функцию, которую можно вызывать с помощью одного параметра:

1
(pf 44)

И это может быть вызвано с 2 параметрами:

1
(pf :defined? 44)

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

Первый палец в воде

Ага. Я думаю, что Clojure довольно мощный. С помощью макросов я добавил в Clojure одну из самых удивительно мощных языковых функций Scala в несколько строк. Вода чувствует себя довольно хорошо до сих пор.

Для справки: у меня были проблемы с Clojure от нашего партнера по JCG Дэвида Поллака в блоге DPP .