I’m digging into delimc, which is pretty awesome stuff, and I ran into a question when writing a shift/reset version of amb:
(use ‘delimc.core)
(defmacro amb [xs]
`(shift k# (dorun (map k# ~xs))))
(reset
(let [a (amb [1 2])
b (amb [:x :y])]
(prn (list a b))))
;; (1 :x)
;; (1 :y)
;; (2 :x)
;; (2 :y)
;=> nil
OK, it’s generating the right stuff and is generally awesome. What I’d really like is to be able to collect each result without side effects.
(defmacro amb [xs]
`(shift k# (map k# ~xs)))
(reset
(let [a (amb [1 2])
b (amb [:x :y])]
(list a b)))
;=> (((1 :x) (1 :y)) ((2 :x) (2 :y)))
This gives me more nesting than I’d like - I was hoping to get:
((1 :x) (1 :y) (2 :x) (2 :y))
If I print out the value of a
and b
at any given point, I only ever have a single value (1
, 2
, :x
, or :y
), so it’s the collection of results, not their emission, that’s at issue here.
For this particular example I can always (mapcat identity …) on the result, but I’d have to do that multiple times if I add more ambs/shifts into the mix.
I definitely don’t fully grasp the CPS transformations that are going on under the hood, but I’m wondering if anyone knows of:
- (a) some way to emit results without side effects (clearly I could just
swap!
/conj
each result into an atom like I did w/ printing), - (b) something inherent to the shift/reset model that makes what I want impossible or not a good idea, or
- (c) a way to change the underlying transformations to get what I want (patch to delimc or other)
In general, the delimc library only works lexically, and not in all cases. Such is the blight of code walkers. If you really want to experiment with delimited continuations, you need runtime support.