Polysemy: Fixpoint
As part of the Haskell folklore, Polysemy supports MonadFix
for with Fixpoint
.
Let's imagine a usecase where we want to find the sources of a source:
type Source = String
type Sources = Map.Map Source Source
sources =
Map.fromList
[ ("4", "3"),
("3", "2"),
("2", "1"),
("1", "0")
]
fetchSource s = do
embedFinal $ putStrLn $ "Asked: " <> show s
Map.lookup s <$> ask
Adding Fixpoint
allows (m)do
statements reordering:
findRoute4Path i = mdo
let fetch x = fromMaybe i <$> fetchSource x
path = init $ concatMap (<> ".") [s0, s1, s2, s3]
s0 <- fetch i
s1 <- fetch s0
s2 <- fetch s1
s3 <- fetch s2
return path
Giving:
Asked: "4"
Asked: "3"
Asked: "2"
Asked: "1"
"3.2.1.0"
See the full the code here.
PS: Actually, a more useful application can be acheive with fix
:
findRoot = fix $ \next s -> fetchSource s >>= maybe (return s) next
PPS: Thanks to Libera's #haskell-fr
folks (and especially to Guillaume Bouchard), we came up with the following example.
MonadFix
tends to be used when you have cross-referencing values.
For example, if you want to emulate OOP's Observer pattern:
data Widget = Widget
{ name :: String,
observers :: [Widget]
}
moveWidget :: Members '[Final IO] r => Widget -> Sem r ()
moveWidget w =
embedFinal $ do
putStrLn $ "Moving " <> w.name <> " done"
forM_ w.observers $ \o ->
putStrLn $ "Moving " <> w.name <> " notify " <> o.name
newWidget :: String -> Widget -> Sem r Widget
newWidget n w = return $ Widget n [w]
observations :: Members '[Fixpoint, Final IO] r => Sem r ()
observations = mdo
marco <- newWidget "Marco" polo
polo <- newWidget "Polo" marco
moveWidget marco
moveWidget polo