简体   繁体   中英

how to avoid nesting in clojure

when my write a function to check a user can delete a post by clojure,I get this

(defn delete!
  {:arglists}
  [^String id]
  (if (valid-number? id)
   (let [result {:code 200 :status "error" :messag "delete success"}]
     (if-let [user (session/get :userid)]
       (if-let [post (pdb/id id)]
         (if (= user (post :user_id))
           (do
             (pdb/delete! (Long/valueOf id))
             (assoc result :status "ok"))
           (assoc result :message (emsg :not-own)))
         (assoc result :message (emsg :post-id-error))))
     (assoc result :message (emsg :not-login)))))

so i want to fix it,i get this

https://github.com/4clojure/4clojure/blob/develop/src/foreclojure/register.clj#L27

https://github.com/4clojure/4clojure/blob/develop/src/foreclojure/utils.clj#L32 but it is line,but not a nest.

the delete! function is nest ugly and it is very hard to understand it,how to write a macro to avoid the nesting a lot.or other way to avoid it.

This doesn't need a macro. I guess cond is a macro, but it is the only one we need to make this code readable.

(defn delete!
  ;; {:arglists} ; this line will not compile
  [^String id]
  (let [result {:code 200 :status "error" :message "delete success"}
        user (session/get :userid)
        post (and user (valid-number? id) (pbd/id id))]
    (cond
      (not user)
        (assoc result :message (emsg :not-login))
      (not post)
        (assoc result :message (emsg :post-id-error))
      (not= user (:user_id post))
        (assoc result :message (emsg :not-own))
      :else
        (do
          (pdb/delete! (Long/valueOf id))
          (assoc result :status "ok")))))

This is something a lot of people run into, so don't feel bad.

Check out this blog by Christophe Grand , which I think is a pretty nice (and concise!) solution.

Edit: you only need something fancy like this (or alternatively the version using delay in this other post ) if you need to short-circuit execution like the original - otherwise noisesmith's answer is the way to go.

Here's how you could do this sort of thing with the Either monad -- I'm sure there are libraries for it already but I'll implement it here for completeness. (Note: this code hasn't been validated.)

(defn success? [v]
  (contains? v :success))

(defn inject [v]
  {:success v})

(defn bind [v f]
  (if (success? v)
    (apply f (:success v))
    v))

(defmacro >>= [v & body]
  (let [binds (map #(list 'bind %) body)]
    `(-> ~v ~@binds)))

(defn delete!
  {:arglists}
  [^String id]
  (if (valid-number? id)
    (let [result {:code 200 :status "error" :message "delete success"}
          check
            (>>= (inject {:id id})
              #(if-let [user (session/get :userid)]
                {:success (assoc % :user user)}
                (:failure (assoc result :message (emsg :not-login))))
              #(if-let [post (pdb/id (:id %))]
                {:success (assoc % :post post)}
                {:failure (assoc result :message (emsg :post-id-error))})
              #(if (= (:user %) ((:post %) :user_id))
                {:success %}
                {:failure (assoc result :message (emsg :not-own))}))]
      (if (success? check)
        (do
          (pdb/delete! (Long/valueOf id))
          (assoc result :status "ok"))
        (:failure check)))))

The >>= macro works like the -> macro (obviously, since it uses it), but if any of the functions return a {:failure ...} then the chain short-circuits (thanks to bind ) and the failure value of the function that failed becomes the value returned by >>= .

Edit

I should note that the function I have named inject is actually called return , but I decided to name it inject here since that's more along the lines of what it does in this monad.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM