Skip to content

Instantly share code, notes, and snippets.

@trptcolin
Last active August 29, 2015 13:57
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save trptcolin/8f91cdb2eddb6e7d9adc to your computer and use it in GitHub Desktop.
Save trptcolin/8f91cdb2eddb6e7d9adc to your computer and use it in GitHub Desktop.

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)
@brandonbloom
Copy link

(defmacro amb [xs]
  `(shift k# (mapcat k# ~xs)))

(reset
  (let [a (amb [1 2])
        b (amb [:x :y])]
    (list a b)))

;=> (1 :x 1 :y 2 :x 2 :y)

@trptcolin
Copy link
Author

Right, but what I really want is to emit pairs (note that the (list a b) is the last thing under the reset)

((1 :x) (1 :y) (2 :x) (2 :y))

@brandonbloom
Copy link

(defmacro choice [& xs]
  `(shift k# (mapcat k# [~@xs])))

(defmacro return [x]
  `(shift k# [(k# ~x)]))

(reset
  (let [a (choice 1 2)
        b (choice :x :y)]
    (return [a b])))

Seems like the delimc has a bug. I wanted to be able to write this:

(reset
  (return
    (let [a (choice 1 2)
          b (choice :x :y)]
      [a b])))

But I get this error:

Exception Please ensure shift is called from within the reset macro.  delimc.core/shift*

@brandonbloom
Copy link

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.

@trptcolin
Copy link
Author

Ah, cool. This does exactly what I'm looking for, thanks. Now the next question I want to understand is: how does choice cause this weirdness:

(reset (let [a (choice 1 2)]) 1)
;; ExceptionInfo Don't know how to create ISeq from: java.lang.Long  clojure.lang.RT.seqFrom (RT.java:505)

@trptcolin
Copy link
Author

OK, I understand the above error now: the mapcat in choice assumes that the result of the continuation call will be seqable. So we need to wrap any results in a seq.

Also, it seems like return isn't buying us much as a macro, and we could actually do:

(def return list)

This would allow your desired version to work:

(reset
  (return
    (let [a (choice 1 2)
          b (choice :x :y)]
      [a b])))
;=> ([1 :x] [1 :y] [2 :x] [2 :y])

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment