• Share
  • Email
  • Embed
  • Like
  • Save
  • Private Content
A little exercise with clojure macro
 

A little exercise with clojure macro

on

  • 550 views

Sharing a practical experience of creating a macro for generalize codes for java interop.

Sharing a practical experience of creating a macro for generalize codes for java interop.

Statistics

Views

Total Views
550
Views on SlideShare
550
Embed Views
0

Actions

Likes
0
Downloads
0
Comments
0

0 Embeds 0

No embeds

Accessibility

Upload Details

Uploaded via as Microsoft PowerPoint

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment

    A little exercise with clojure macro A little exercise with clojure macro Presentation Transcript

    • A little exercise with Macro Zehua Liu
    • Prerequisites• Basic macro (defmacro mywhen [cond-exp & clauses] `(if ~cond-exp (do ~@clauses)))• Java interop (String. "a string") (.substring "a string" 0 5)
    • Motivation• A script to test a server with a request /response protocol• To send a request to the server, reuse Java classes that represent requests in server code
    • Java Request Classes
    • Java Request Classes
    • Sending Login Request
    • Sending SendMessage Request
    • Sending Requests• Many other types of requests• Lots of duplicate codes• Refactor them!• function?
    • Refactor using function(defn send-request [req-pkt sid] (let [[new-sid resp-pkts] (send-req req-pkt sid)] (if-let [err-msg (check-resp-error resp-pkts)] (do (println (format "ERROR: %s failed, sid=%s,req=%s,err=%s" (.getName req-pkt) sid req-pkt err-msg)) [nil nil]) [new-sid resp-pkts])))
    • Refactor using function
    • Refactor using function• Not too bad• Can we do better / differently?• macro?
    • Login Request Again
    • Make it work for Login Request
    • Make it work for Login Request(defmacro send-request [request username password sid] `(let [req-pkt# (doto (requests.LoginRequest.) (.setUsername ~username) (.setPassword ~password)) [new-sid# resp-pkts#] (send-req req-pkt# ~sid)] (if-let [err-msg# (check-resp-error resp-pkts#)] (do (println (format "ERROR: %s failed, sid=%s,req=%s,err=%s" ~(name request) ~sid req-pkt# err-msg#)) [nil nil]) [new-sid# resp-pkts#])))(defn login [:Login username password sid] (send-request username password sid))
    • Make it work for Login Request
    • Make it work for Login Request(defmacro send-request [request username password sid]`(let [req-pkt# (doto (new ~(symbol (str "requests." (name request) "Request"))) (.setUsername ~username) (.setPassword ~password)) [new-sid# resp-pkts#] (send-req req-pkt# ~sid)] (if-let [err-msg# (check-resp-error resp-pkts#)] (do (println (format "ERROR: %s failed, sid=%s,req=%s,err=%s" ~(name request) ~sid req-pkt# err-msg#)) [nil nil]) [new-sid# resp-pkts#])))(defn login [username password sid] (send-request :Login username password sid))
    • Make it work for Login Request(.setUsername ~username) <==> (. setUsername ~username)(.setUsername ~username) <==>(. (symbol (str "set" (name :Username))) ~username)(.setUsername ~username)(.setPassword ~password) <==>~@(map (fn [[pn pv]] `(. ~(symbol (str "set" (name pn))) ~pv)) {:Username username :Password password})
    • Make it work for Login Request(defmacro send-request [request param-map sid] `(let [req-pkt# (doto (new ~(symbol (str "requests." (name request) "Request"))) ~@(map (fn [[pn pv]] `(. ~(symbol (str "set" (name pn))) ~pv)) param-map)) [new-sid# resp-pkts#] (send-req req-pkt# ~sid)] (if-let [err-msg# (check-resp-error resp-pkts#)] (do (println (format "ERROR: %s failed, sid=%s,req=%s,err=%s" ~(name request) ~sid req-pkt# err-msg#)) [nil nil]) [new-sid# resp-pkts#])))(defn login [username password sid] (send-request :Login {:Username username :Password password} sid))
    • Refactor using macro(defn login [username password sid] (send-request :Login {:Username username :Password password} sid))(defn send-message [message dest-username type sid] (send-request :SendMessage {:Message message :DestUsername dest-username :Type type} sid))
    • Refactor using macro• It works! Hooray!• Let’s use it for more fancy stuff.• Optional request fields?• On server side, SendMessage type default to 1, if not specified
    • Optional field(defn send-message [message dest-username type sid] (send-request :SendMessage {:Message message :DestUsername dest-username :Type type} sid))(defn send-message [message dest-username sid] (send-request :SendMessage {:Message message :DestUsername dest-username} sid))
    • Optional field(defn send-message ([message dest-username sid] (send-message message dest-username nil sid)) ([message dest-username type sid] (let [param-map-base {:Message message :DestUsername dest-username} param-map (if type (assoc param-map-base :Type type) param-map-base)] (send-request :SendMessage param-map sid))))
    • Optional field, FAILED(defn send-message ([message dest-username sid] (send-message message dest-username nil sid)) ([message dest-username type sid] (let [param-map-base {:Message message :DestUsername dest-username} param-map (if type (assoc param-map-base :Type type) param-map-base)] (send-request :SendMessage param-map sid))))CompilerException java.lang.IllegalArgumentException: Dontknow how to create ISeq from: clojure.lang.Symbol, compiling:(NO_SOURCE_PATH:10)
    • Optional field, FAILED
    • Optional field, FAILED• macro is evaluated at compile time, not at run time• macro evaluator only knows about symbols – {:Username username :Password password} is a map (keywords to symbols) – But param-map is a symbol – At compile time, macro evaluator does not know the run time value that the symbol param-map represents• How do we fix it?
    • Optional field, Fixing it• How do we fix it?• One thought: – Tell the macro the complete list of fields, and have it generate codes like below for every field:(if-let [v (:Type param-map)] (. setType v))• And then param-map can be a symbol whose value macro evalutor does not need to know, its value is only needed at run time.
    • Optional field, Fixing it(defmacro send-request [request param-list param-map sid] (let [param-map# param-map r (gensym)] `(let [req-pkt# (let [~r (new ~(symbol (str "requests." (name request) "Request")))] ~@(map (fn [pn] `(if-let [pv# (~pn ~param-map#)] (. ~r ~(symbol (str "set" (name pn))) pv#))) param-list)) [new-sid# resp-pkts#] (send-req req-pkt# ~sid)] (if-let [err-msg# (check-resp-error resp-pkts#)] (do (println (format "ERROR: %s failed, sid=%s,req=%s,err=%s" ~(name request) ~sid req-pkt# err-msg#)) [nil nil]) [new-sid# resp-pkts#]))))
    • Optional field, Fixing it(defn login [username password sid] (send-request :Login [:Username :Password] {:Username username :Password password} sid))(defn send-message ([message dest-username sid] (send-message message dest-username nil sid)) ([message dest-username type sid] (let [param-map-base {:Message message :DestUsername dest-username} param-map (if type (assoc param-map-base :Type type) param-map-base)] (send-request :SendMessage [:Message :DestUsername :Type] param-map sid))))
    • Optional field, Fixing it(macroexpand (send-request :SendMessage [:Message :DestUsername :Type] param-map nil))(let* [req-pkt__625__auto__ (clojure.core/let [G__671 (new requests.SendMessageRequest)] (clojure.core/if-let [pv__624__auto__ (:Message param-map)] (. G__671 setMessage pv__624__auto__)) (clojure.core/if-let [pv__624__auto__ (:DestUsername param-map)] (. G__671 setDestUsername pv__624__auto__)) (clojure.core/if-let [pv__624__auto__ (:Type param-map)] (. G__671 setType pv__624__auto__))) vec__672 (scratch.core/send-req req-pkt__625__auto__ nil) new-sid__626__auto__ (clojure.core/nth vec__672 0 nil) resp-pkts__627__auto__ (clojure.core/nth vec__672 1 nil)] (clojure.core/if-let [err-msg__628__auto__ (scratch.core/check-resp-error resp-pkts__627__auto__)] (do (clojure.core/println (clojure.core/format "ERROR: %s failed, sid=%s,req=%s,err=%s" "SendMessage" nil req-pkt__625__auto__ err-msg__628__auto__)) [nil nil]) [new-sid__626__auto__ resp-pkts__627__auto__]))
    • Lessons Learned• macro is evaluated at compile time, not at run time• macro evaluator treats code as data – {:Username username :Password password} is a map of keywords to symbols, not keywrods to strings (or whatever type username/password might be) – But param-map is a symbol• At compile time, macro evaluator treats ref/var as symbol, knowing nothing about their run time values