Fork me on GitHub
#code-reviews
<
2020-09-05
>
stopa04:09:50

Ah, fixed this by writing interleave like this

(defn interleave* [s1 delayed-s2]
  (cons-stream (car-stream s1) (interleave* (my-force delayed-s2) (my-delay (cdr-stream s1)))))

(defmacro interleave [s1 s2]
  `(interleave* ~s1 (my-delay ~s2)))
I think the error above came from a fundamental misunderstanding of macros, and was worsened by the fact that interleave already exists:
(defmacro interleave [s1 s2]
  `(cons-stream (car-stream ~s1) (interleave ~s2 (cdr-stream ~s1))))
I defined this as a recursive function, but i realize this can’t work for macros — it would need to expand all the time. I guess it evaled because interleave inside the macro referred to cojure.core/interleave

seancorfield04:09:43

@U0C5DE6RK you can have recursive macros, but they have to operate on the form passed in, not the value:

user=> (defmacro trial [x]
  (if (seq x)
    `(do (println ~(first x)) (trial ~(rest x)))
    :done))
#'user/trial
user=> (trial [1 2 3])
1
2
3
:done
user=>
This breaks down the sequence passed in directly and either returns a form that recursively expands only the tail of the sequence, or a non-recursive expression :done

seancorfield04:09:17

user=> (macroexpand '(trial [1 2 3]))
(do (clojure.core/println 1) (user/trial (2 3)))
user=> (macroexpand '(trial (2 3)))
(do (clojure.core/println 2) (user/trial (3)))
user=> (macroexpand '(trial (3)))
(do (clojure.core/println 3) (user/trial ()))
user=> (macroexpand '(trial ()))
:done
user=>

stopa03:09:09

Interesting, thanks Sean! Will play with this more

stopa21:09:25

Hey Sean, tried playing with this a bit: Based on trial, it seems like interleave should work as a recursive macro too (as it only works on the forms)

(defmacro rec-interleave [s1 s2]
  `(cons-stream (car-stream ~s1)
                (rec-interleave ~s2 (cdr-stream ~s1))))
macroexpand seems to do what I expect:
(macroexpand '(rec-interleave integers integers))

=> [(chp3/car-stream integers) (chp3/my-delay (chp3/rec-interleave integers (chp3/cdr-stream integers)))]
Buut, if I try to evaluate (def x (rec-interleave integers integers))
Execution error (NoClassDefFoundError) at nrepl.middleware.interruptible-eval/evaluate (interruptible_eval.clj:79).
Could not initialize class clojure.lang.Compiler$CompilerException
--- Am not quite sure how to debug further from that error. Do you have thoughts on what could be happening?

seancorfield22:09:04

@U0C5DE6RK My first suspicion would be that it is an infinite evaluation. But I always advise debugging things in a plain REPL, without nREPL and all that other stuff that tries to "help" manage evaluation and display of results.

❤️ 3
seancorfield22:09:42

Without seeing a self-contained, repeatable project up on GitHub containing this code, it would be hard for me to suggest anything further.

stopa17:09:46

Okay I think I understand why my thing is not working. Indeed it is trying to eval in a loop

stopa17:09:09

I am not sure how my interleave is different from the trial one: Perhaps the big thing is the if — if doesn’t eval the the forms until runtime, while mine evals at compile time.

seancorfield17:09:55

Just looked at this -- now I can see what the other functions/macros are -- and you're falling into the same trap I talked about above: your macroexpansion has no "bottom" at compile time so it just recursively expands until you get a stackoverflow.

👍 3
seancorfield17:09:01

The example I gave works because it is analyzing forms at compile time and so it can tell when it hits the end of a form. It wouldn't work with bound names because those are not sequence forms.

seancorfield17:09:19

(rec-interleave integers integers) expands to [(interleave.demo/car-stream integers) (interleave.demo/my-delay (interleave.demo/rec-interleave integers (interleave.demo/cdr-stream integers)))] but in trying to evaluate that the remaining rec-interleave call is macroexpanded again and it always produces a larger form. Hence the infinite loop during evaluation.

stopa17:09:49

Am close but don’t quite get the difference between trial and interleave: > It wouldn’t work with bound names because those are not sequence forms. Do you mind expounding a bit? What is the difference between a bound name and a sequence form? i.e:

(defmacro trial [x]
  (if (seq x)
    `(do 
      (println ~(first x)) 
      (trial ~(rest x)))
    :done))
How does the compiler know that it shouldn’t do anything when it sees trial in the do expression?

seancorfield17:09:46

It does do something -- it expands it. But pay attention to what actually happens as you expand it each time:

user=> (macroexpand '(trial [1 2 3]))
(do (clojure.core/println 1) (user/trial (2 3)))
user=> (macroexpand '(trial [2 3]))
(do (clojure.core/println 2) (user/trial (3)))
user=> (macroexpand '(trial [3]))
(do (clojure.core/println 3) (user/trial ()))
user=> (macroexpand '(trial []))
:done
user=> 

seancorfield17:09:11

It's processing the form [1 2 3] at compile-time, not the runtime value [1 2 3].

seancorfield17:09:31

And the form shrinks on each expansion so it bottoms out.

seancorfield17:09:06

But in

user=> (def x [1 2 3])
#'user/x
user=> (macroexpand '(trial x))
Syntax error macroexpanding trial at (REPL:12:1).
Don't know how to create ISeq from: clojure.lang.Symbol
user=> 
the x is not a sequence so the compile-time call to seq fails -- x is not evaluated here because we're in the Read phase still, we haven't reached evaluation, so there are no runtime values.

seancorfield17:09:19

Macros work on code, not values.

stopa18:09:14

Aah I see! So if we had passed in (trial [foo bar]) It would do something like print ’foo ’bar --- Oky doke this makes a lot of sense. Thanks Sean!

3