Fork me on GitHub
#beginners
<
2020-10-17
>
stopa03:10:42

Hey team, say I have a function like this:

(def b (fn [a] a))

(def x (fn [a] (b a))) 
I want to write a function called “expand”:
(expand '(x 1))
; => 
'((fn [a] ((fn [a] a) a))
This would in essence “replace” all definitions with the source code To do this, I thought about first writing a new kind of fn macro, which remembers the source code:
(defmacro fn# [args & body]
  (with-meta
    `(fn ~args ~@body)
    {:args '~args
     :body '~body}))
This way, I expand can walk my form and replace from the metadata However, very quickly I came across an issue:
(meta (fn# [a] (+ 1 a)))
Syntax error compiling at (/private/var/folders/sz/rnrd6cv509x2t8nbm6k4mzsh0000gn/T/form-init16854188530357342528.clj:1:7).
Unable to resolve symbol: args in this context
I think I am making a noob mistake here:
{:args '~args
 :body '~body}
Tried a bunch of variations: Just args, ~args, '~args, but no luck. If someone has thoughts would appreciate it!

andy.fingerhut03:10:36

I think ~'args might be what you want

andy.fingerhut03:10:00

~ inside of a syntax-quoted expression means sort of "don't syntax-quote the next subexpression". If you use ~args, the thing that gets inserted into the syntax-quoted expression is args, but evaluated at compile time. If you use ~'args, then 'args is evaluated at compile time, which is just the symbol args

andy.fingerhut04:10:34

Did some experimenting, because I always get this kind of stuff wrong on first guess without trying it out:

(defmacro fn1 [args & body]
  (with-meta
    `(fn ~args ~@body)
    `{:args '~args
      :body '~body}))

❤️ 3
stopa04:10:05

Ah, amazing. Thanks @U0CMVHBL2!

stopa04:10:08

Ah, I see, the syntax-quote was necessary around our meta map, because otherwise there would be no way to use ~

stopa04:10:58

Okay, final question for the day team. Say I have a namespace: I want to “monkey-patch” def with this:

(ns church-factorial
  (:refer-clojure :exclude [def]))

(defmacro def [name v]
  `(do
    (clojure.core/def ~name ~v)
    (alter-meta! (var ~name) assoc :source {:name '~name :v '~v})
    (var ~name)))
The exclude works, but I’m not sure how to access the original def. I don’t think it’s just defined in clojure.core/def

andy.fingerhut04:10:32

I don't think that is possible except via modifying Java code in the Clojure compiler.

andy.fingerhut04:10:51

def is one of the handful of special forms that are not implemented as functions or macros.

andy.fingerhut04:10:45

The :exclude [def] does not give an error, but it does not actually mean that def is undefined within the namespace, either:

$ clj
Clojure 1.10.1
user=> (ns foo (:refer-clojure :exclude [def]))
nil
foo=> (def bar 5)
#'foo/bar
foo=> bar
5

andy.fingerhut04:10:18

As opposed to +, say, which you can actually exclude, and it will not be defined in that namespace:

$ clj
Clojure 1.10.1
user=> (ns foo (:refer-clojure :exclude [+]))
nil
foo=> (+ 1 2)
Syntax error compiling at (REPL:1:1).
Unable to resolve symbol: + in this context

andy.fingerhut04:10:59

You can find a list of special forms here, and I would guess that for any of them, you won't get an error if you exclude them, but they will be like def in their behavior -- still defined if excluded, and cannot be monkey-patched from within Clojure itself: https://clojure.org/reference/special_forms

andy.fingerhut04:10:44

fn and let might be exceptions in that list, since they actually are implemented as macros, with fn* and let* being the underlying special forms they are based upon.

dpsutton05:10:43

also, I think if you just use def there the compiler doesn't attempt to look special forms up in the environment

dpsutton05:10:14

❯❯❯ clj
Clojure 1.10.1
(defmacro def [name v]
  `(do
     (def ~name ~v)
     (alter-meta! (var ~name) assoc :source {:name '~name :v '~v})
     (println "i defined " ~name)
     (var ~name)))
#'user/def
user=> (def x 3) ;; refers to clojure def
#'user/x
user=> (user/def x 5) ;; must always have a namespace or alias to refer to def so it doesn't resolve to clojure's
i defined  5
#'user/x
user=>

stopa17:10:39

Learned a lot, thank you team!

teodorlu10:10:49

(late to the party) Clojure Spec provides its own def, and uses clojure.core/def in the namespace that provides the new def: https://github.com/clojure/spec.alpha/blob/master/src/main/clojure/clojure/spec/alpha.clj#L349

ampersanda08:10:58

Hello, I try to run -main function in a fresh Clojure CLI project but I got this error.

➜  contsscraper.app git:(main) ✗ clj -A:run        
Execution error (FileNotFoundException) at clojure.main/main (main.java:40).
Could not locate constsscraper/core__init.class, constsscraper/core.clj or constsscraper/core.cljc on classpath.

Full report at:
/var/folders/gv/ry9mmtjj0vx87_ppthlrc4nr0000gn/T/clojure-4702191647831092797.edn
To be honest, I don’t know what’s wrong with my setup, anyone could help? I attach screenshot that might useful Thank you

Alex Miller (Clojure team)13:10:13

That looks right to me, unless there’s some spelling error in the namespaces somewhere that I didn’t catch

Alex Miller (Clojure team)13:10:58

Might be worth trying clj -Sforce -A:run to make sure your classpath isn’t cached wrong somehow

Alex Miller (Clojure team)13:10:48

Then I’d back out of the alias and just do clj -m constsscraper.core

Alex Miller (Clojure team)13:10:37

Something doesn’t line up here, just need to jiggle it enough to see what

ampersanda14:10:40

Thank you for the replies, I missed the spelling of the namespace https://clojurians.slack.com/archives/C053AK3F9/p1602930866127900

kimim08:10:36

Hello, is there any good plotting lib which can generate vector graphics, for scientific papers. Thanks.

👍 3
practicalli-johnny13:10:33

https://hypirion.github.io/clj-xchart/ will generate SVG (vector graphics) I have use Oz for data visualisation, I didnt check to see if it creates vector graphics. The hiccup syntax can be used to create SVG graphics, which is just a data structure. https://clojurebridgelondon.github.io/workshop/introducing-clojure/clojure-svg-graphics.html I havent tried these libraries yet, but they may be of use https://keminglabs.com/c2/ http://liebke.github.io/analemma/

kimim01:10:26

thank you. I will try clj-xchart first

ampersanda14:10:29

ah thanks, I didn’t see that

dharrigan10:10:33

You're missing a s

stopa18:10:59

Hey team, one more wacky question:

(defn make-code [x] 
  ...)
(make-code '(foo 1 2)) 
I want this to return:
(
  ; bar
  foo 
  1 2)
I want to “insert” in the “; bar” comment In effect, is there a way I can make clojure “create” a form with comments inside? : O

seancorfield18:10:03

@stopachka Not if you're producing symbolic code (you'd have to produce a string instead).

👍 3
stopa18:10:30

I see, this makes sense. Thanks Sean!

seancorfield18:10:10

(because comments are handled by the reader, and the reader turns strings into data structures/code and removes the comments)

❤️ 3
seancorfield18:10:37

You could always produce (do (comment "bar") (foo 1 2)) -- the comment form is executable and returns nil 🙂

❤️ 3
andy.fingerhut18:10:28

There are some libraries written with the goal of enabling you to read text representing Clojure code/data, and return not what clojure.core/read or clojure.edn/read would return, but instead a custom data structure that represents all white space, comments, etc. and let you manipulate that. e.g. https://github.com/xsc/rewrite-clj I have not used them, so can't comment on all of the possible gotchas and limitations they might have.

❤️ 3
schmee18:10:04

I saw this thing from borkdude just the other day, seems to fit the bill perfectly! https://github.com/borkdude/rewrite-edn

❤️ 3
borkdude18:10:07

For creating / manipulating code while preserving whitespace / comments rewrite-clj is my goto library. I recommend using https://github.com/lread/rewrite-cljc-playground since that's going to be the maintained future of both rewrite-clj(s)(c)

❤️ 3
borkdude18:10:29

It will be transferred to clj-commons later on

borkdude18:10:24

rewrite-edn is a lib on top of rewrite-cljc that can help with editing config files while preserving comments

Joe21:10:15

Is there a way to watch activity on an async chan without taking from it? I tried to do something like (tap (mult channel-to-watch) put-on-this-channel) and then but it seemed to mess with the activity on the channel-to-watch

Joe21:10:17

This works (though is not good because of the sleep)

(let [program [3,26,1001,26,-4,26,3,27,1002,27,2,27,1,27,26
                 27,4,27,1001,28,-1,28,1005,28,6,99,0,0,5]
        c1-in (async/chan)
        c1-out (run-async program c1-in)
        c2-out (run-async program c1-out)
        c3-out (run-async program c2-out)
        c4-out (run-async program c3-out)
        c5-out (run-async program c4-out)]
    (async/pipe c5-out c1-in)
    (>!! c1-in 9)
    (>!! c1-out 8)
    (>!! c2-out 7)
    (>!! c3-out 6)
    (>!! c4-out 5)
    (>!! c1-in 0)
    (Thread/sleep 5000)
    (<!! c1-in))

phronmophobic21:10:04

what about poll!?

Joe21:10:10

What I'm trying to do is set up a tap on ch1-in (or I suppose ch5-out) so I can see what's getting put on there - the final value before the channel closes is what I want. This doesn't work - it throw a null pointer somewhere much further up in the code, which makes me think that the tap is impeding the pipe flow between c1-in and c5-out

(let [program [3,26,1001,26,-4,26,3,27,1002,27,2,27,1,27,26
                 27,4,27,1001,28,-1,28,1005,28,6,99,0,0,5]
        c1-in (async/chan)
        c1-out (run-async program c1-in)
        c2-out (run-async program c1-out)
        c3-out (run-async program c2-out)
        c4-out (run-async program c3-out)
        c5-out (run-async program c4-out)
        read (async/chan)]
    (async/pipe c5-out c1-in)
    (async/tap (mult c1-in) read)
    (>!! c1-in 9)
    (>!! c1-out 8)
    (>!! c2-out 7)
    (>!! c3-out 6)
    (>!! c4-out 5)
    (>!! c1-in 0)
    (async/<!! (async/go-loop [outputs []]
                 (let [val (async/<! read)]
                   (println "read got val" val)
                   (if val (recur (conj outputs val))
                       outputs)))))

Joe21:10:15

> what about poll!? Won't that take the value off the chan? I'm trying to not impact the machine that's running in the background, just look at the traffic

Joe21:10:16

> Takes a val from port if it's possible to do so immediately.

Joe21:10:03

My worry would be that if I take the val while polling, then it won't get taken by the actual thing that's meant to be consuming it

phronmophobic21:10:37

yea, I don't think poll! is what you want here

phronmophobic21:10:03

in general, it's easier to test each individual process and then put everything together than to try to test everything at once

phronmophobic21:10:54

you could "spy" on values flowing through by wrapping each link in the chain with a process that just prints the value passing through before piping it to the next channel

phronmophobic21:10:08

something like:

(defn spy-process [prefix ch1 ch2]
  (go-loop [v (<! ch1)]
    (when v
      (prn prefix v)
      (>! ch2 v)
      (recur))))

;; eg.
(spy-process "ch1->ch2" ch1 ch2)

Joe21:10:17

> in general, it's easier to test each individual process and then put everything together than to try to test everything at once Thanks, this is good advice. I'm getting closer I think, by simplifying the machines a bit:

(defn simple-run [inchan name]
    (let [outchan (async/chan)]
      (async/go-loop [counter 0]
        (if (> counter 10) (do (async/close! outchan))
            (do (println name counter)
                (async/>! outchan (inc (async/<! inchan)))
                (recur (inc counter)))))
      outchan))
  
  (let [in (async/chan)
        out1 (simple-run in :machine1)
        out2 (simple-run out1 :machine2)]
    (async/pipe out2 in)
    (>!! in 100)
    (Thread/sleep 1000)
    (<!! in))
  
  (let [in (async/chan)
        out1 (simple-run in :machine1)
        out2 (simple-run out1 :machine2)
        read (async/chan 1000)
        m (async/mult out2)]
    (async/tap m in)
    (async/tap m read)
    (>!! in 100)
    (<!! (async/go-loop [outputs []]
           (let [val (async/<! read)]
             (if val (recur (conj outputs val))
                 outputs)))))

Joe21:10:27

I think trying to mix and match pipes and mults in the original was a bad idea. The last one still isn't working - It breaks my repl, I think it's not terminating or something, but I can't figure out why

Joe22:10:47

OK, I guess the read channel isn't closing on it's own properly, since this works OK

(def read (async/chan 1000))

  (let [in (async/chan)
        out1 (simple-run in :machine1)
        out2 (simple-run out1 :machine2)
        m (async/mult out2)]
    (async/tap m in)
    (async/tap m read)
    (>!! in 100))

  (async/close! read)

  (<!! read)

phronmophobic22:10:48

if there's an exception, I don't think simple-run will close the channel which might be causing your last block to hang

phronmophobic22:10:24

you'll probably want to wrap the go-loop in a try/catch and close the channel in the catch clause

Joe22:10:22

I don't think there is any exception being thrown, because I can run it corectly 'by hand', just pulling values of the read chan until it nils. I don't see anything in the docs for mult about it automatically closing when its out2 closes. Maybe that's it?

Joe22:10:15

I would think it would though...

Joe22:10:00

OK, works fine when I increase the buffer

(defn simple-run [inchan name]
    (let [outchan (async/chan 100)]
      (async/go-loop [counter 0]
        (if (> counter 10) (do (async/close! outchan))
            (do (println name counter)
                (async/>! outchan (inc (async/<! inchan)))
                (recur (inc counter)))))
      outchan))

  (let [in (async/chan 100)
        out1 (simple-run in :machine1)
        out2 (simple-run out1 :machine2)
        m (async/mult out2)
        read (async/chan 100)]
    (async/tap m in)
    (async/tap m read)
    (>!! in 100)
    (async/<!! (async/go-loop [outputs []]
                 (let [val (async/<! read)]
                   (println "read got val" val)
                   (if val (recur (conj outputs val))
                       outputs)))))
So I guess what's happening with no buffer is that 1. machine 1 sends it's final output to machine 2, then stops 2. machine 2 sends it's final output the mult (to machine 1 and read), then stops 3. machine 1 is stopped, so will never take the val of it's input. This blocks the (synchronous) mult, which never closes, and so read also never closes

🎉 3
jsn22:10:18

can you use a chan transducer for spying?