Мои ноги промокли от Clojure
Я начал жесткое кодирование в Project Plugh . Я работаю над переносом нескольких концепций Lift на Clojure, когда создаю кометные возможности Lift в Clojure, чтобы я мог передавать данные в браузер.
Задний план… PartialFunction
В Scala есть PartialFunction
Ключевым понятием для PartialFunctions является «… это унарная функция, в которой домен не обязательно включает все значения типа A.»
PartialFunction
Очень полезна возможность проверить, содержит ли домен определенное значение. pf.isDefinedAt(x)
позволяет проверить, определена ли функция с заданным значением x
.
Но a PartialFunction
является подклассом Function
, поэтому PartialFunctions
может применяться:
pf(x)
Компилятор Scala возьмет шаблон и превратит его в PartialFunction
:
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
состоит в том, что они могут быть составлены:
pf = pf1 orElse pf2 orElse pf3 // pf isDefinedAt any place // any of the partial functions are defined
Мы PartialFunctions
широко используем в Lift, чтобы разрешить выбор, должен ли конкретный URL обслуживаться Lift, должен ли он обслуживаться определенным обработчиком REST и т. Д. Например, определение маршрута REST в Lift:
serve { case "api" :: "user" :: AsLong(userId) :: _ GetJson _ => User.find(userId).map(_.toJson) }
Поскольку я изучал Clojure в рамках подготовки к презентации в Strange Loop и как часть нового проекта, над которым я работал , я стремлюсь привнести лучшие вещи в Lift в код Clojure, который я пишу.
В Clojure
Материал соответствия Clojure довольно изящен. Особенно мне нравится , как вы можете извлечь значения из карты (это так гораздо более мощным , что согласование в Scala модель, даже … но исключить его я отвлекся).
Итак, я написал макрос:
(defmacro match-func [& body] `(fn [~'x] (match [~'x] ~@body)))
Это создает функцию, которая является применением сопоставления с параметром, поэтому:
((match-func [q :guard even?] (+ 1 q) [z] (* 7 z)) 33) ;; 231
Оказывается, шаблонизатор Clojure будет извлекать значения в несвязанные переменные. Но связанные переменные проверяются … это означает, что:
((match-func [[x y]] (+ x y)) [4 5])
Оказывается, это проблема, потому что x
она связана с match-func
макросом … поэтому нам нужно перейти x
на что-то другое. Итак, мы должны изменить переменную x
на что-то другое:
(defmacro match-func [& body] "Create a function that does pattern matching." `(fn [x#] (match [x#] ~@body)))
isDefinedAt
Итак, как мы можем проверить, соответствует ли шаблон определенному значению?
Это стало для меня проблемой, чтобы обернуть свой мозг вокруг того, как все делается в Clojure. Как у меня есть функция, которая представляет сопоставление с образцом, и я могу запросить его, чтобы определить, определен ли он в точке, не вызывая вычисления на правой стороне, что может иметь побочные эффекты.
Ответ арности. Scala имеет функции с определенной арностью. Оказывается, что Clojure может иметь единственную функцию, которая ведет себя по-разному в зависимости от арности вызова. Ура!
Итак, макрос выглядит так:
(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))))))
Это дает нам функцию, которую можно вызывать с помощью одного параметра:
(pf 44)
И это может быть вызвано с 2 параметрами:
(pf :defined? 44)
И я добавил возможность получить тело оригинала, чтобы я мог добавить orElse
функцию, которая фактически создаст новую, PartialFunction
то есть компиляцию составленных шаблонов, чтобы шаблоны были скомпилированы более эффективно.
Первый палец в воде
Ага. Я думаю, что Clojure довольно мощный. С помощью макросов я добавил в Clojure одну из самых удивительно мощных языковых функций Scala в несколько строк.
Вода чувствует себя довольно хорошо до сих пор.