Fork me on GitHub
#code-reviews
<
2020-09-29
>
stopa21:09:06

(def instruction-label first)
(defn ->label+instruction [raw-instructions]
  (reduce (fn [res part]
            (if (symbol? part)
              (conj res [part nil])
              (let [last-label (instruction-label (last res))]
                (conj res [last-label part]))))
          []
          raw-instructions))

(comment
  (->label+instruction '((assign)
                         label-one
                         (test)
                         (branch)
                         label-two
                         (goto))))

=> [[nil (assign)] [label-one nil] [label-one (test)] [label-one (branch)] [label-two nil] [label-two (goto)]]
Hey team, wrote up a quick function, which given a [sym|list], transforms it like so: [[curr-sym list]] If there’s a better way to write this function, would appreciate it! (It’s used in a machine language simulator: https://github.com/nezaj/clj-sicp/blob/master/src/machine_simulator.clj#L21-L27)

seancorfield21:09:57

Instead of using last, I would probably track the current symbol in the accumulator part.

seancorfield21:09:28

Using peek instead of last would be much faster -- last will walk the whole of res every time.

😮 3
seancorfield21:09:14

(second (reduce (fn [[sym res] part] 
                  (if (symbol? part) 
                    [part (conj res [part nil])] 
                    [sym (conj res [sym part])])) 
                [nil []] 
                raw-instructions))

stopa21:09:48

Great point, thanks Sean! cc @joeaverbukh

seancorfield21:09:50

(untested but I think that'll work)

stopa21:09:34

works great!

stopa21:09:55

(defn label-idx [transformed-instructions label]
  (->> transformed-instructions
       (map-indexed (fn [i x] [i (instruction-label x)]))
       (filter (comp (partial = label) last))
       ffirst))

(comment
  (label-idx '[[nil (assign)] [label-one nil] [label-two nil]]
             'label-two))
; => 2
The idea of label-idx is to find the first idx where the instruction-label is = the given label Seems like a lot of work to do above — thoughts on better one much appreciated!

stopa21:09:27

(def indexed (partial map-indexed vector))

(defn positions [filter-fn coll]
  (->> coll
       indexed
       (filter (fn [[_ v]] (filter-fn v)))
       (map first)))

(defn label-idx [label instructions]
  (first (positions (comp (partial = label) instruction-label)
                    instructions)))

(comment
  (label-idx
    'label-two
    '[[nil (assign)] [label-one nil] [label-two nil]]))
^ wrote above. I see there used to be a clojure.contrib.seq-utils, but it is now deprecated. lmk if other way to do this : }

seancorfield21:09:24

@stopachka That's a case where SICP is somewhat hampered by only having lists as data structures. In Clojure, you'd probably use a different data structure, or a pair of data structures, to efficiently track where labels referred.

❤️ 3
seancorfield21:09:40

The whole of clojure.contrib went away between Clojure 1.2 and 1.3. Some parts of it became new Contrib libraries, the rest of it just fell by the wayside. Here's some of that fallout: https://clojure.org/dev/contrib_history

👍 3
seancorfield21:09:29

Regarding searching for the first index of a given label, I'd probably use reduce and return (reduced idx) when I hit a match.

❤️ 3
seancorfield21:09:42

(defn label-idx [label instructions]
  (reduce (fn [idx [sym instr]]
            (cond (= label sym) (reduced idx) 
                  (= ::sentinel sym) (reduced -1)
                  :else
                  (inc idx)))
          0
          (conj instructions [::sentinel nil])))

seancorfield21:09:09

Or you could use a local gensym to create a more unique sentinel item...

stopa22:09:30

Thanks Sean! Makes sense — will play with keeping an auxiliary data struct for labels — something like label->idx! Makes sense

stopa22:09:47

If you get time, thoughts on:

(defn extract-labels [raw-instructions]
  (rest (reduce
          (fn [[idx label->idx instructions] part]
            (if (symbol? part)
              [idx
               (assoc label->idx part (inc idx))
               instructions]
              [(inc idx)
               label->idx
               (conj instructions part)]))
          [-1 {} []]
          raw-instructions)))

(comment
  (let [[label->idx instructions] (extract-labels '((assign foo (const 1))
                                                    label-one
                                                    (test (op =) (const 1) (reg foo))
                                                    (branch (label label-two))
                                                    label-two
                                                    (assign bar (const 2))))]
    (println label->idx)
    (println (map-indexed vector instructions))))
extract-labels now returns two things:
a. a mapping of label->idx 
b. a cleaned up list of instructions
Full code: https://github.com/nezaj/clj-sicp/blob/master/src/machine_simulator.clj#L11-L65

seancorfield23:09:44

Yup, that's probably a good way to deal with it. I suspect there's a more optimized way that could rely on the shared nature of persistent data structures... Probably a single pass that produces an actual list of instructions (not a vector) such that you could have your map from labels to actual code so you could naturally iterate through the instructions steps and have the label lookup return the sequence of instructions to continue executing. You'd probably need to construct that via lazy-seq to avoid blowing the stack since you'd essentially process it recursively.

❤️ 3
stopa23:09:12

Ah, very interesting! Thanks Sean