polysemy-research / polysemy

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

Nested mixed effects? #446

Open eyeinsky opened 2 years ago

eyeinsky commented 2 years ago

Is something like the following possible in polysemy where given the effects

data E1 :: Effect where
  E1_unit :: E1 m ()
  E1_rec :: something.. a -> E1 m a

makeSem ''E1

data E2 :: Effect where
  E2_unit :: E2 m ()
  E2_rec :: something.. a -> E2 m a

makeSem ''E2

one could write something like this:

main = do
  e1_rec $ do
    e1_unit
    e2_unit
    e2_rec $ do
      e1_unit
      e2_unit
  e1_rec $ do
    e1_unit
    e2_unit
  ..

I.e I arbitrarily mix different effects under both effects' _rec variants and the effect stack is same on each "level".

My actual use case is that I have DSLs for generating css and javascript both of which have their separate effects/GADTs and interpreters, and am wondering if I could re-use them without writing a sum GADT.

googleson78 commented 2 years ago

This works, for something.. = m. Am I misunderstanding your question?

{-# LANGUAGE TemplateHaskell #-}

module Bla where

import Polysemy

data E1 :: Effect where
  E1_unit :: E1 m ()
  E1_rec :: m a -> E1 m a

makeSem ''E1

data E2 :: Effect where
  E2_unit :: E2 m ()
  E2_rec :: m a -> E2 m a

makeSem ''E2

bla :: Member E1 r => Member E2 r => Sem r ()
bla = do
  e1_rec $ do
    e1_unit
    e2_unit
    e2_rec $ do
      e1_unit
      e2_unit
  e1_rec $ do
    e1_unit
    e2_unit
googleson78 commented 2 years ago

Of course, this doesn't restrict the do block to only contain E1 and E2 in it, which might be an issue for you?

eyeinsky commented 2 years ago

@googleson78 thanks! I had briefly tried it, but backed out as I couldn't figure out how could I use the m in interpretation (also using interpretH, and just now getting it to work with pureT is scary..).

In the "hard part" below I'd essentially like to run all the effects to the end using the current state, but modify the writer before writing it in to the "main interpretation":

-- | E1 is meant to be interpreted to these effects:
type E1_Base = '[State Int, Writer String]

-- | Interpret E1 in terms of E1_Base
e1ToBase :: forall r . (Members E1_Base r) => InterpreterFor E1 r
e1ToBase = interpretH $ \case
  E1_unit -> do
    modify @Int (+1)
    tell @String "this"
    pureT ()
  E1_rec m -> do
    -- the hard part:
    -- state0 <- get @Int
    -- let
    --   (writer, (state1, a)) = runWriter $ runState state0 $ ..?.. $ m
    --   modifiedWriter = "begin " <> writer <> " end"
    -- put @Int state1
    -- tell @String modifiedWriter
    -- return a
    return undefined