Thanks! For the record, here is how to achieve what I want by
explicitly using `runChildren` and `stopRecursion`:
testSplice :: Splice IO
testSplice = do
input <- getParamNode
kids <- runChildren
stopRecursion
return [input { elementChildren = kids }]
It surprises me that an explicit call to `runChildren` is necessary,
especially after your comment regarding sensitivity to evaulation
order.
Your linked post shows that Heist splices are processed top down,
which reminds me of the `transform` combinator in Uniplate:
http://community.haskell.org/~ndm/downloads/paper-uniform_boilerplate_and_list_processing-30_sep_2007.pdf
The authors discuss bottom-up and top-down transformations in Sections
2.3 and 2.4 and argue for providing bottom-up transformations and only
a specific form of top-down transformations.
I think Heist's splice processing would be more intuitive (less
sensitive to evaluation order?) if applied bottom up rather than top
down. This only seems to require a slight change in the definition of
`runNode` from the post you linked - to process children before
applying the splice:
runNode :: Monad m => X.Node -> Splice m
runNode (X.Element nm at ch) = do
newAtts <- mapM attSubst at
newKids <- runNodeList ch -- added this line
let n = X.Element nm newAtts newKids -- changed this line
s <- liftM (lookupSplice nm) getTS
maybe n (recurseSplice n) s -- changed this line
-- removed local function `runKids`
runNode n = return [n]
This change would simplify the definition of filter splices which
would not need to call `runChildren` explicitly. It would also make
the definition of substitution splices more uniform, because children
would be already processed when applying the splice - just like
attributes are.
Are Heist splices processed top down intentionally? (Reasons for doing
so are the same reasons people might have for preferring call-by-name
over call-by-value. However, I tend to agree with the discussion in
the Uniplate paper and would prefer "call-by-value" aka bottom-up
transformation.)
Best,
Sebastian