# Chaining method calls and “bubbling” the results

I need a way to build up a sort of stack of places to search for an item.

What I would like to do is to have each layer searched for an item, and if it is found at that level, then it should be loaded by each higher level (successively).

What I have so far is just a basic framework, but I was wondering if I was missing some more apt abstraction here in terms of command flow. Does anyone have any suggestions?

type Result = String
type Query = String
type Search = Query -> Maybe Result
type Load = Result -> Maybe Result

search :: Search,

data RunFunc = RunFunc { runner :: [RunFunc] -> Maybe Result }

mkRunFunc :: Query -> LoadFunc -> RunFunc
mkRunFunc q lf = RunFunc r
where r (x:xs) =
case search lf q of
Nothing -> runner x xs >>= load lf
a       -> a
r []    = search lf q

where search q = if q == "hi" then return $"Found in mem." else Nothing load = addToLog "Loaded to mem." renDisk :: LoadFunc renDisk = LoadFunc { search = search, load = load } where search q = if q == "foo" then return$ "Found rendered." else Nothing

where search q = if q == "bar" then return $"Found on disk." else Nothing load = addToLog "Now on disk." addToLog :: Result -> Result -> Maybe Result addToLog next str = return$ str ++ (' ':next)

tester :: [LoadFunc] -> Query -> Maybe Result
tester caches q = runner first rem
where caches' = mk fmap caches
rem     = tail caches'
mk      = mkRunFunc q


To see this in action, it should be sufficient to load up ghci and enter:

tester "bar" [inMem,renDisk,onDisk]


How about making inMem & co "bracket" functions? Like follows:

import Control.Monad.Writer

type M a = Writer String a
type LoadFunc a b = (a -> M b) -> a -> M b

type Query = String
type Result = ()

inMem _ "hi" = tell "Found in mem. "
inMem f q    = f q >> tell "Added to mem. "

tester = foldr ($) notFound where notFound q = tell "Not found!"  Run using runWriter$ tester [inMem,renDisk,onDisk] "bar"
This should give you the control flow you seek - as well as enough room for extension by replacing the monad as required. You could for example have notFound throw an exception using an Error monad instead (which would probably make sense).