Apply smiley replacement filter to Pandoc but not in code segments I wrote a filter for pandas that converts :-) to ☺ but not in inline code or code blocks. Pandoc makes it so the applyExceptOnCodeBlock smileyfy function trickles through the whole tree. But since I operate on Blocks and Inlines I need to handle every data constructor for Block that takes Inlines.

import Text.Pandoc.JSON
import Text.Pandoc.Walk

import Data.List (intercalate)
import Data.List.Split (splitOn)

main :: IO ()
main = toJSONFilter $ applyExceptOnCodeBlock smileyfy

smileyfy :: String -&gt; String
smileyfy = intercalate "☺" . splitOn ":-)"


applyExceptOnCodeInline :: (String -&gt; String) -&gt; Inline -&gt; Inline
applyExceptOnCodeInline f (Str s)       = Str (f s)
applyExceptOnCodeInline f (Code attr s) = Code attr s -- for code nothing shall happen
applyExceptOnCodeInline _ x = x


applyExceptOnCodeBlock :: (String -&gt; String) -&gt; Block -&gt; Block
applyExceptOnCodeBlock f (Plain is) = Plain $ applyExceptOnCodeInline f &lt;$&gt; is
applyExceptOnCodeBlock f (Para  is) = Para  $ applyExceptOnCodeInline f &lt;$&gt; is
applyExceptOnCodeBlock f (LineBlock iss) = LineBlock $ (fmap . fmap) (applyExceptOnCodeInline f) iss
applyExceptOnCodeBlock f (RawBlock format s)  = RawBlock format $ f s
applyExceptOnCodeBlock f (DefinitionList [(is, bss)]) = DefinitionList [(applyExceptOnCodeInline f &lt;$&gt; is
                                                                        ,fmap (fmap (applyExceptOnCodeBlock f)) bss)]
applyExceptOnCodeBlock f (Header i attr is) = Header i attr $ applyExceptOnCodeInline f &lt;$&gt; is
applyExceptOnCodeBlock f (Table is as ds tcs tcss) = Table (applyExceptOnCodeInline f &lt;$&gt; is) as ds tcs tcss
applyExceptOnCodeBlock _ x = x --this covers CodeBlock as well


And thus it looks very convoluted. Is there a clearer way to express this?
There's a much shorter way to express the same functionality, but I'm not sure you'll immediately find it to be clearer.

import Data.List (intercalate)
import Data.List.Split (splitOn)
import Text.Pandoc.JSON

main :: IO ()
main = toJSONFilter smile

smile :: Inline -&gt; Inline
smile (Str s) = Str . intercalate "☺" . splitOn ":-)" $ s
smile x = x


I think the insight you missed is that the Block constructor CodeBlock contains no Inlines, so you can trust the Walkable typeclass machinery to ignore them if your smiley making code only works on Inlines. That eliminates the entire applyExceptOnCodeBlock function.

Similarly it isn't necessary to include a case for Inline's Code constructor, constructors like Code and Math contain their own raw strings and are safely ignored. Pandoc's datatypes are already structured to divide prose text from markup making it easy to define transformations by only matching on the things you do want to modify.