Lift state monad to focus on part of a record

70 views Asked by At

With the following code snippet:

data Circle = Circle 
            { center :: Point
            , radius :: Double
            }

data Point = Point (Double, Double)

someFuncOverPoint :: State Point blahblah

I wonder if there is a function that can make the someFuncOverPoint to focus on Circle:

someMagicFunc :: ??? -> State Point blahblah -> State Circle blahblah

Maybe this can be implemented using lens?

2

There are 2 answers

0
willeM_ Van Onsem On BEST ANSWER

Strictly speaking you can. Indeed, we can create a state by first generating the first State Point a, and then pass that into a State, so:

someMagicFunc :: Double -> State Point a -> State Circle a
someMagicFunc r s = State (\s0 -> let ~(s1, a) = runState s s0 in (Circle s1 r, a))

So here we construct a State that works with a function that maps the initial state s0 to the next state s1 and the result, and then we turn that into a 2-tuple with the Circle as state, and the "result" a as well.

That being said, it is a bit strange to change the type of the state. Usually the type of the state remains the same over all the actions.

0
pe200012 On

Thanks to @willeM_VanOnsem's answer.

The motivation here is that I have some hierarchical data structures and some effectful functions that only focus on part of the context, so I need a way to compose them.

I think there's a better way:

someMagicFunc :: State Point a -> State Circle a
someMagicFunc s = state (\s0 -> let ~(a, s1) = runState s (center s0)
                                in (a, s0 { center = s1 }))

And we can reverse the arrow:

revMagic :: Double -> State Circle a -> State Point a
revMagic r s = state (\s0 -> let ~(a, s1) = runState s (Circle s0 r)
                             in (a, center s1))

UPDATE: After some research, I figure out that what I need is in fact zoom with type zoom :: Monad m => Lens' s a -> StateT a m r -> StateT s m r.

Generally, it's trivial if we want to downcast the context in state monad, but if we want to upcast, we may need something like prism to construct the data.