просто для удовольствия: я бы мысленно расширил и обобщил вашу задачу на переписывание структур данных в соответствии с некоторыми правилами, чтобы вы могли объявлять (возможно, рекурсивные) правила перезаписи для преобразования любого ввода в любой желаемый вывод в целом. (и на практике clojure)
давайте начнем с простой функции преобразования:
(defn convert [rules data]
(if-let [res (some (fn [[condition rewrite]]
(when (condition data) (rewrite data)))
rules)]
res
data))
она находит первое правило, которое соответствует вашему вводу (если есть), и применяет его функцию преобразования:
(def my-rules [[sequential? (fn [data] (map #(convert my-rules %) data))]
[number? inc]
[keyword? (comp clojure.string/upper-case name)]])
#'user/my-rules
user> (convert my-rules [:hello :guys "i am" 30 [:congratulate :me]])
;;=> ("HELLO" "GUYS" "i am" 31 ("CONGRATULATE" "ME"))
при таком подходе ваши правила будут выглядеть примерно так:
(def rules
[[(every-pred coll? (comp #{'not} first)) (fn [data] (map (partial convert [[#{'not} (constantly 'nor)]]) data))]
[(every-pred coll? (comp #{'or} first)) (fn [data] (map (partial convert [[#{'or} (constantly 'nor)]]) data))]
[(every-pred coll? (comp #{'and} first)) (fn [[_ & t]] (cons 'nor (map #(list 'nor %) t)))]])
#'user/rules
user> (convert rules '(and x y z))
;;=> (nor (nor x) (nor y) (nor z))
хорошо, это работает, но выглядит довольно некрасиво. Тем не менее, мы можем исключить некоторые повторения, введя несколько основных функций для контролеров и преобразователей:
(defn first-is
"returns a function checking that the input is collection and it's head equals to value"
[value]
(every-pred coll? (comp #{value} first)))
, преобразовав ваши правила в:
(def rules
[[(first-is 'not) (fn [data] (map (partial convert [[#{'not} (constantly 'nor)]]) data))]
[(first-is 'or) (fn [data] (map (partial convert [[#{'or} (constantly 'nor)]]) data))]
[(first-is 'and) (fn [[_ & t]] (cons 'nor (map #(list 'nor %) t)))]])
#'user/rules
user> (convert rules '(and x y z))
;;=> (nor (nor x) (nor y) (nor z))
, а затем введя заменяющее правило перезаписи:
(defn replacing
([new] [(constantly true) (constantly new)])
([old new] [#{old} (constantly new)]))
, ведущий нас к
(def rules
[[(first-is 'not) (fn [data] (map (partial convert [(replacing 'not 'nor)]) data))]
[(first-is 'or) (fn [data] (map (partial convert [(replacing 'or 'nor)]) data))]
[(first-is 'and) (fn [[_ & t]] (cons 'nor (map #(list 'nor %) t)))]])
, теперь мы можем видеть, что есть потребность в функции, преобразующей каждый элемент в коллекции. давайте введем это:
(defn convert-each [rules]
(fn [data] (map #(convert rules %) data)))
(def rules
[[(first-is 'not) (convert-each [(replacing 'not 'nor)])]
[(first-is 'or) (convert-each [(replacing 'or 'nor)])]
[(first-is 'and) (fn [[_ & t]] (cons 'nor (map #(list 'nor %) t)))]])
user> (convert rules '(or x y z))
;;=> (nor x y z)
user> (convert rules '(and x y z))
;;=> (nor (nor x) (nor y) (nor z))
теперь это намного лучше, но последнее предложение все еще немного уродливо. Я могу подумать о введении функции, которая преобразует голову и хвост по отдельным правилам, а затем объединяет преобразованную голову и хвост:
(defn convert-cons [head-rules tail-conversion]
(fn [[h & t]] (cons (convert head-rules h) (tail-conversion t))))
(defn transforming [transformer]
[(constantly true) transformer])
(def rules
[[(first-is 'not) (convert-each [(replacing 'not 'nor)])]
[(first-is 'or) (convert-each [(replacing 'or 'nor)])]
[(first-is 'and) (convert-cons [(replacing 'nor)]
(convert-each [(transforming #(list 'nor %))]))]])
user> (convert rules '(and x y z))
;;=> (nor (nor x) (nor y) (nor z))