Может быть, в кложуре

Попытка написать составную функцию в clojure, которая завершается при первом нулевом значении (например, что-то, что вы могли бы сделать, объединив Maybes вместе в haskell) со следующим:

(defn wrap [f] (fn [x] (if (nil? x) nil (f x))))

(defn maybe [arg & functs] ( (comp (reverse (map wrap functs))) arg))

Чтобы я получил, например.

(defn f1 [x] (+ x 1))

(maybe 1 f1 f1 ) => 3

(maybe nil f1 f1) => nil

Что, к сожалению, дает мне следующее: ClassCastException clojure.lang.PersistentList не может быть приведен к пользователю clojure.lang.IFn/возможно (NO_SOURCE_FILE:1)

Может ли кто-нибудь помочь в том, что я делаю неправильно здесь? Какой идиоматический способ сделать это?


person Steve B.    schedule 14.05.2015    source источник
comment
что делает wrap (и x (f x )) не делает?   -  person RedDeckWins    schedule 14.05.2015


Ответы (4)


Идиоматический способ сделать это — использовать some->. См. документацию этого макрос для более подробной информации.

Не позволяйте этому помешать вам сделать свой собственный, конечно!

person WolfeFan    schedule 14.05.2015
comment
Обратите внимание, что результирующее использование немного отличается; макрос some-› красиво проталкивает ваш результат неявно через формы и не компонуется, тогда как функция may требует реальных функций, но компонуется. например (some->> [1] (map inc)) но (maybe [1] #(map inc %)) - person Dax Fohl; 14.05.2015
comment
@DaxFohl вы имеете в виду, что это не компонуется, потому что это макрос? В чем разница между двумя фрагментами в вашем комментарии? - person muhuk; 15.05.2015
comment
@muhuk Да, это не компонуется, потому что это макрос; если вы хотите во время выполнения создать свой список функций для выполнения таким образом, вы не сможете сделать это с помощью some->. Разница в том, что (map inc) значительно короче, но это выражение не будет компилироваться вне макроса some->. - person Dax Fohl; 15.05.2015
comment
Понятно. Однако на самом деле это не делает some-> несоставным. Также вы все еще можете (def f #(map inc %)), а затем (some->> [1] (f)), если необходимо. - person muhuk; 15.05.2015
comment
@muhuk сказал, что у тебя было (defn runall [arg & functs] ((comp (reverse functs)) arg)), просто maybe без wrap. Вы можете во время выполнения выбирать между ними (let [myfunc (if whatever maybe runall)] (myfunc input f1 f2 f3)). Невозможно сделать это с some->. Вот что люди имеют в виду, когда говорят, что макросы не компонуются. - person Dax Fohl; 15.05.2015

comp ожидает каждую функцию как отдельный аргумент, но вы передаете ей список функций как один аргумент. Чтобы обойти это, используйте apply.

(defn maybe [arg & functs] ( (apply comp (reverse (map wrap functs))) arg))
person Dax Fohl    schedule 14.05.2015

Всегда есть пространство имен clojure.algo.monads с монадой maybe-m:

(with-monad maybe-m
  (defn adder [x]
    (let [f (fn [x] (+ x 1))]
      (domonad
        [a x
         b (f a)
         c (f b)]
       c))))

(adder 1)
=> 3 

(adder nil)
=> nil

По общему признанию, это может быть немного излишним для ваших требований

person Mark Fisher    schedule 15.05.2015

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

Прочитав эту статью о многопоточности монад, я смог прийти к добавьте следующее, расширив макрос m->, определенный в статье, чтобы создать многопоточную монаду, возможно, для более простого использования. TBH, нет ничего проще, чем просто использовать some->, но это было из личного любопытства.

Хорошо, для начала нужно определить какой-то нестандартный код, вот (на случай, если статья когда-нибудь исчезнет) определение многопоточной монады Джайлза:

(defn bind-monadic-expr-into-form [insert form]
  (list 'm-bind insert
        (if (seq? form)
          `(fn [bound#] (~(first form) bound# ~@(rest form)))
          `(fn [bound#] (~form bound#)))))

(defmacro m->
  ([m x]
   `(with-monad ~m ~x))
  ([m x form]
   `(with-monad ~m
                ~(bind-monadic-expr-into-form x form)))
  ([m x form & more]
   `(m-> ~m (m-> ~m ~x ~form) ~@more)))

Теперь с этим вы можете определить макрос с нитями, возможно, как

(defmacro maybe->
  ([x] `(m-> ~maybe-m ~x))
  ([x form] `(m-> ~maybe-m ~x ~form))
  ([x form & more] `(maybe-> (maybe-> ~x ~form) ~@more)))

И используйте его как:

(maybe-> 1 inc)
=> 2

(maybe-> [1 2] (#(map inc %)))
=> (2 3)

(defn f1 [x] (+ 1 x))
(maybe-> 1 f1 f1)
=> 3

(maybe-> 1 f1 ((constantly nil)) f1)
=> nil

(maybe-> {:a 1 :b 2} :c inc)
=> nil

В этом контексте нет абсолютно никакого преимущества в использовании этого по сравнению с some->, но монада m-> действительно добавляет некоторые интересные возможности в возможности создания макроса fail->, как в статье, на которую я дал ссылку, которая предлагает больше, чем просто "ноль" в качестве возврата, давая Вы способность дифференцировать причину отказа.

person Mark Fisher    schedule 18.05.2015