Example
First list: (1 2 3 4)
Second list: (* slot (- slot (/ slot slot)))
Output: (* 1 (- 2 (/ 3 4)))
The first list's elements will be inserted to the second list.
The symbol slot
in second list means the position for insertion.
My Solution
I have written a code snippet and it works for the above example.
(define (insert-slot numbers slots)
(cond
[(null? slots)
'()]
;; operate on the nested list and come back to the element after
[(pair? (car slots))
(cons
(insert-slot numbers (car slots)) ;; nested list
(insert-slot numbers (cdr slots)))] ;; element after
;; insert number to slot
[(equal? (car slots) 'slot)
(cons (car numbers)
(insert-slot (cdr numbers) (cdr slots)))]
;; just take the original element in slots
(else
(cons (car slots)
(insert-slot numbers (cdr slots))))))
Problem
However, the second clause(operate on the nested list) of the cond has some problems, the numbers
used in the nested list and the element after should be different. The later numbers
is the result of the previous numbers
. It works correctly for the example above, but if the second list likes this (* (+ slot slot) (- slot slot))
, it will get the wrong answer.
I figured out that I can store states in the numbers
and return different value based on the times it has called, but it's not like the Scheme way(no extra stored data). Is there a simple way to solve this problem ?
TL;DR: yes, with continuation-passing style.
Very interesting question! Nested list is a tree, so you're merging a linear list of values into a tree's fringe, according to the presence of a keyword
'slot
there.So we start, as is usual in structural recursions tasks, with traversing a tree to just create an exact copy of it, to get a hang of it,
Next we linearize it by going CPS,
and then we thread our state through, traversing the list of given values to substitute for each instance of the keyword:
We call it as
This can be further cleaned up by adding error checking, and converting to an internal definition with shorter name and fewer arguments.
edit: In a pattern-matching pseudocode, which might be easier to read/follow, it is
This computational pattern of threading a changing state through the computations is exactly what (Haskell's) State monad is about; the above would be written there (sans the particulars of the "tree" type) as
where the state - our "values" - is passed around as part of a "stateful computation pipeline" implicitly, with automatically correct sequencing (which we had to take care of manually, using the
avals
anddvals
carefully each in its appropriate place in our Scheme code); andevalState
performs this whole combined computation while discarding the end state, returning only the finally computed value just like our collector/continuation-function(lambda (v r) r)
did.Turning the latter into the former is a matter of a purely syntactic transformation. So it's nothing special, this "monad" thing, after all. It's a computational pattern, explicated, captured.