[Question\Help] SOLVED: Interpreter but not interpreter e... - Polysemy

Welcome to the Functional Programming Zulip Chat Archive. You can join the chat here.


I'm not sure how do I even call the thing I want to do. Nor do I know how to do it :D

Okay, in the end I want to get interactive program with output like this:

<spacebar press>
<spacebar press>
...and so on ad infinitum till ^C do us part

Condensed minimal example from actual code. Written in browser, so may not compile, but gives general idea.

data ClockEffect m a where
  Change :: ThingEffect m ()

makeSem ''ClockEffect

data Clock = Tick | Tock

reinterpretClockToStateWithTrace :: Member Trace r => Sem (ClockEffect ': r) a -> Sem r a
reinterpretClockToStateWithTrace = reinterpret $ \case
  Change -> do current <- get
                              case current of
                                   Tick -> trace "Tock" >> put Tock
                                   Tock -> trace "Tick" >> put Tick

driveClockFromKeyboard :: Member (Embed IO) r => _ -> _ -- I have no idea if this should produce ClockEffect or handle it or what
driveClockFromKeyboard = do forever $ void hGetChar >> change

runClockIO :: IO ()
runClockIO = runM . traceToIO . reinterpretClockToStateWithTrace . driveClockFromKeyboard -- or something like that?

Oh yeah, I'm using Polysemy- from stackage.


Just as usual after posting a question somewhere I solved it somehow on my own. Why I'm not so clever until I actually post something?

Anyway, solution:

keyboardClock :: Members [Embed IO, ClockEffect] r => Sem r a
keyboardClock = do embed prepareIO
                   forever $ waitForInput >> change

test :: IO ()
test = void
     . runM
     . traceToIO
     . runState Low
     . reinterpretClockToState
     $ keyboardClock

Apparently I was right with type of keyboardClock initially, but mistaken in test it should've had $ instead of . before last effect.

Alex Chapman

Asking the question often clarifies your thoughts enough to help you see the answer yourself. My old boss wanted to get me a teddy bear to talk to, so I wouldn't have to tell him my problems only to solve them myself without any input from him.

Alex Chapman

So I guess your reinterpretClockToState has a type like Sem (ClockEffect ': r) a -> Sem (State Clock ': r) a?

Georgi Lyubenov // googleson78

your example can be implemented like this:
you want is to execute change forever
you want "change" to mean "output Tick, input a char, output Tock, input a char"
where "output" means to putStrLn and "input" means to getChar
translated into a program:

 , DataKinds
 , FlexibleContexts
 , GADTs
 , LambdaCase
 , PolyKinds
 , RankNTypes
 , ScopedTypeVariables
 , TypeApplications
 , TypeOperators
 , TypeFamilies
 , BlockArguments

import Polysemy
import Polysemy.Embed (embed)
import Polysemy.Input (Input, input, runInputSem)
import Polysemy.Output (Output, output, runOutputSem)

import Control.Monad (forever)

data ClockEffect m a where
  Change :: ClockEffect m ()

makeSem ''ClockEffect

clockTickInputTockInput :: Members '[Input Char, Output String] r => Sem (ClockEffect ': r) a -> Sem r a
clockTickInputTockInput = interpret \case
  Change -> do
    output "Tick"
    _ <- input @Char -- these should be unnecessary with the plugin
    output "Tock"
    _ <- input @Char
    pure ()

-- it's important to leave the list of effects polymorphic:
-- * we can reuse program in more places now
-- * clockTickInputTockInput won't be able to work if we specify the effect list entirely, because it requires the list to contain Input and Output
--   the instance resolution mechanism handles this, instantiating to the appropriate list of effects
program :: Member ClockEffect r => Sem r ()
program = forever change

-- runOutputSem allows us to treat every output action as a monadic action with access to the output value
-- runInputSem allows us to treat every input action as a monadic action, providin the input value
-- embed allows us to "embed" monads in our effect stack - IO in this case
-- and when we're left with only Embed IO in our list, we can dispatch it with runM
main :: IO ()
  = runM
  $ runOutputSem (embed . putStrLn)
  $ runInputSem (embed getChar)
  $ clockTickInputTockInput program

@Alex Chapman yes, just a usual reinterpreter from effect to another.
@Georgi Lyubenov // googleson78 not slow, but "gave a additional pov". I didn't even thought about using Input/Output effects. That should be more clear, actually - I also want Clock to be actual clock with tunable frequency and maybe pure thing for tests. Using Input/Output gives flexibility to achieve that. So thank you