Fork me on GitHub
#beginners
<
2017-04-10
>
samueldev01:04:34

so I've moved along nicely in my macro'ing so far

samueldev01:04:41

but am tripping up on my macro needing to write a defn

samueldev01:04:55

specifically that I want to bind something in the macro, and then have that outputted as the arguments to the defn

samueldev01:04:16

but because of the way bindings work after compile time, I don't think that's a thing I can do right? because I can't like

samueldev01:04:29

have bindings permeate up from macro-time into regular-time

samueldev01:04:29

(defmacro foo [a b c]
  (let [d "test"]
    `(defn [d] (str d "bar"))))

samueldev01:04:31

contrived example

noisesmith01:04:21

so you want to create d inside the macro, and for that d to be used by the macro? are you intending that "test" become the name of the arg to the defn or? also that defn is missing a name, I don't know if that is on purpose

noisesmith01:04:33

I'm not really sure what d should be doing here

samueldev01:04:40

not intentional, typo

samueldev01:04:54

hmm how better to explain this

samueldev01:04:30

basically the macro is to get rid of a bunch of boilerplate. every function that I am exposing in this SDK I'm writing has basically the same first 20 lines or so

samueldev01:04:40

and it's all just validation against the arguments + spec

samueldev01:04:05

so anyway, ignoring some gross code / probably me not doing this the most efficient way I can be

samueldev01:04:30

(defmacro def-sdk-fn [name spec topic body]
  `(defn ~name
     ([& args#]
      (let [args# (map iu/extract-params args#)
            callback# (second args#)]
        (if-let [error# (cond

                          (or (> (count args#) 2))
                          (do (js/console.log "args > 2")
                              (e/wrong-number-of-args-error))

                          (and (first args#)
                               (not (map? (first args#))))
                          (do (js/console.log "first arg not map")
                              (e/invalid-args-error))

                          (and (not (nil? callback#))
                               (not (fn? callback#)))
                          (do (js/console.log "cb isnt a fn")
                              (e/invalid-args-error))

                          :else false)]
          (p/publish {:topics ~topic
                      :error error#})
          (let [params# (first args#)
                params# (assoc params# :callback (second args#))]
            (if (not (s/valid? ~spec params#))
              (do (js/console.info "Params object failed spec validation: " (s/explain-data ~spec params#))
                  (p/publish {:topics ~topic
                              :error (e/invalid-args-error)}))
              ~body)))))))

samueldev01:04:34

there's the macro itself

samueldev01:04:26

and how I'd like to use it is basically this:

(def-sdk-fn login
  ::login-spec
  "cxengage/authentication/login-response"
    (do (js/console.log "test" topic)
          (p/publish {:topics topic
                             :response "aww yiss"})))

samueldev01:04:46

note the "topic" usage in my using the macro

samueldev01:04:21

that's something that I'm both: - passing to the macro itself (3rd argument after name and spec) - wanting to be available as a binding to the ~body that they pass in

noisesmith01:04:32

OK - this is called an anaphoric macro

noisesmith01:04:36

they are well documented

noisesmith01:04:55

clojure tends to discourage them, but they are possible by using a ' to prevent evaluation inside a ~ unquote

noisesmith01:04:07

so you end up with ~'topic inside the code

noisesmith01:04:12

also, the idomatic way to do a body for a macro is [... & body] in the args and then [email protected] in the code, then you don't need the do block in the input

noisesmith01:04:07

for usage of topic in the body of the call to work, you might need something like [~'topic ~topic] in the let block

samueldev01:04:01

and my usage of the macro doesn't change in that case?

samueldev01:04:07

I just have a magical "topic" binding accessible to me

samueldev01:04:12

without visibly seeing where it comes from?

samueldev01:04:22

(if so, I can see why these are discouraged)

noisesmith01:04:54

right, like I said these are called anaphoric macros, if you google for "anaphoric macro clojure" you'll probably see a few good examples of using the ~'foo combo

samueldev01:04:10

well it works

samueldev01:04:17

but i have that "dont do this gross thing" spidey feeling

samueldev01:04:21

thank you! 🙂

drewverlee14:04:49

Would it be resonable to solve problems the “Bridge Design Pattern” using multimethods? It seems to me the goal is to have flexibility in extending our types and the functions that operate over those types.

drewverlee15:04:24

Can a defrecord have two protocals it references?

yonatanel15:04:46

@drewverlee Do you have an example? The text book shape-drawing-itself one doesn't seem like idiomatic clojure.

drewverlee15:04:57

@yonatanel The goal here is educational, I both want to understand a bit about these patterns and see how they relate between java and clojure. The second goals is that several of my co-workers have experessed their inerested in FP, but aren’t sure how to apply their oop patterns to FP. I assume many of the patterns to be some form of missing language feature.

drewverlee15:04:45

Best java example (i think at least) was from this: http://www.journaldev.com/1491/bridge-design-pattern-java

yonatanel15:04:02

Frankly in this kind of discussion you'll have to show them how to solve real problems, because in clojure you'll just have {:shape :circle, :color :red} and any number of functions that can draw them with different implementations.

drewverlee15:04:18

I agree. It helped a lot of have someone else echo my thoughts. An actual problem would probably be better, but also more time consuming and harder to understand, ill see what i can come up with 🙂

yonatanel15:04:16

@drewverlee I'm interested if you're trying to convince everyone to switch to clojure at the workplace or if you're just talking about it, and how it goes :)

agile_geek16:04:23

@drewverlee here is a link to one way to tackle the problem Bridge pattern is trying to solve using Clojure http://mishadoff.com/blog/clojure-design-patterns/#bridge This example uses multi methods as you suggest but also introduces hierarchy in the keywords to dispatch on (i.e. keywords can derive from other keywords). Lots of other interesting patterns in the link BTW

drewverlee16:04:43

Thanks agile geek, i have worked though some of those. I really like it, but I wanted to create something more technically in depth

agile_geek16:04:01

@drewverlee I think that Bridge pattern example shows the fundamental difference between OOP and FP in it's approach. In the FP version only the data has a hierarchy. I.e. data can inherit but behaviour cannot. You can dispatch to different implementations of behaviour based on data. It's the clean separation of data and behaviour that makes it powerful. I don't know how often in OOP I've made the point that you should not inherit only for behaviour but delegate. It's great to have a language that reinforces that concept.

drewverlee16:04:03

Very well put. That's a great way of comparing the two styles.

polymeris20:04:04

Hi. Is there any way to "alias" a classname? trying to do something like this:

(def assertion-error
  #?(:clj java.lang.AssertionError :cljs js/Error))
;...
(is (thrown? assertion-error (assert nil)))
Works in cljs, but clj complains about being "Unable to resolve classname: assertion-error"

noisesmith20:04:50

since is is a macro, you might need to write a macro for this

noisesmith20:04:09

(or rewrite is to evaluate the first arg to a thrown? clause)

noisesmith20:04:36

iirc is uses a multimethod, and one of the method dispatches hard-codes the path for thrown?

polymeris20:04:40

i see, thanks

polymeris20:04:11

yes, you are correct regarding use of a multimethod (which is exactly the same in clj and cljs, btw)

noisesmith20:04:43

so you could just use defmethod to override that multi... monkey-patch style - this may be a bad idea though

polymeris20:04:55

i guess I could write a assertion-thrown?

noisesmith20:04:10

oh yeah - and extend it yourself - that's much more sensible

noisesmith20:04:32

I recently had a related problem (which is why I know about is using a multimethod etc. etc.) it turns out that futures capture Exceptions and you can try/catch around the deref, but AssertionErrors don't behave as nicely / testably. I basically just gave up on using AssertionErrors so that the code would be testable.

polymeris20:04:31

no futures here -- this is cljs 😛

noisesmith20:04:58

well, no AssertionErrors in cljs either...