polysemy-research / polysemy

:gemini: higher-order, no-boilerplate monads
BSD 3-Clause "New" or "Revised" License
1.04k stars 73 forks source link

Document `Polysemy.Reader.local` #441

Open yaitskov opened 2 years ago

yaitskov commented 2 years ago

How to use local function? Googling was also not sufficient.

googleson78 commented 2 years ago

local comes from the traditional Reader monads local so searching for that might yield more resources. I'm not aware of interpretations other than that one.

In the traditional Reader, local is implemented by post-composing a function with our "Reader environment":

> runReader ask 5
5
> runReader (local succ ask) 5
6
> runReader ask [1,2]
[1,2]
> runReader (local (4:) ask) [1,2]
[4,1,2]
> runReader (local (4:) (asks (42:))) [1,2]
[42,4,1,2]

It's applicable when you want to use a "state-like" effect, but you don't care about the state persisting after your action runs. My go-to examples are getting the depth of a tree in a contrived/overcomplicated way:


import Control.Monad.Trans.Reader

data Tree a
  = Empty
  | Node (Tree a) a (Tree a)

depth :: Tree a -> Int
depth t = runReader (go t) 0
  where
    go :: Tree a -> Reader Int Int
    go Empty = ask
    go (Node l _ r) = do
      l' <- local succ $ go l
      -- note how we also need to local succ for the right subtree,
      -- because the succ from above doesn't "carry over" to here
      r' <- local succ $ go r
      pure $ max l' r'

and working with nameless lambda terms, where when you enter a lambda, you want to shift all the variables inside the body of the lambda up by one (in a similar manner with something like local succ again).

tek commented 2 years ago

I think the takeaway of this might be that we should include documentation for effects even if they're just replications of common functionality from base