Do raised effects interact with ones already in your stack? - Polysemy

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

Georgi Lyubenov // googleson78

If I use raise to introduce an A into my effects stack, will how I dispatch that A affect any other As already present in the stack? (inspiration for this question or maybe I'm just not understanding what's going on there)

Hi, I've been pulling my hair for the last couple of days on this issue, but I can't manage to find a solution, I would appreciate if anyone can help me! Goal Use together Polysemy and Pers...
Torsten Schmits

I think that raise is just there to line up the types, because of the possibility to switch interpreters mid-interpretation

Georgi Lyubenov // googleson78

but how is the started transaction passed in from there supposed to get used for the interpretation for the leftover Database effect?

Torsten Schmits

so you're talking about the raiseUnder2, not the raise, right

Torsten Schmits

my guess would be that the other Database will be consumed by another interpreter that is basically not doing anything

Georgi Lyubenov // googleson78

to me it seems the opposite - this one does nothing, and the other one that is not seen here does the work

Georgi Lyubenov // googleson78

but the op did say transactions are working..

Torsten Schmits

Georgi Lyubenov // googleson78 said:

but the op did say transactions are working..

I wanted to write the same thing to the letter :joy:

Georgi Lyubenov // googleson78

so that's where my original question came from - there must be something about effect interactions/semantics that I'm missing

Torsten Schmits

it looks like op is guessing that Database is in the stack of the higher order thunk in Transaction. so if it is, does it get executed in bracket? is that the question we're having?

Georgi Lyubenov // googleson78

even if you guess right, raise introduces new effects, no?

Georgi Lyubenov // googleson78

imagine if you introduced a new State and then the interpretation of that state messed with the "other" State you have further down your list off effects :thinking:

Torsten Schmits

if you want a more in-depth experience of this problem, I recommend this clash of the titans between Love and Lexi: https://github.com/hasura/eff/issues/12
:joy:

First-order effect systems like freer-simple can make use of interpreters/effect interception to have pseudo higher-order actions, like handleError and catchError. Unfortunately, such actions have ...
Georgi Lyubenov // googleson78

I'm following that, but I think that the instance of my question is not directly related to what they're discussing (I think!*)

Georgi Lyubenov // googleson78

I honestly can't follow most of what Alexis is saying, even though she describes it in great detail
probably lacking a few key abstractions

Torsten Schmits

it's probably buried in there somewhere!

Georgi Lyubenov // googleson78

I think this (my original question) would just be "unsound" in some sense of the word, if it did work like this

Georgi Lyubenov // googleson78

you would never be able to rely on introducing a new effect and interpreting it, for fear of it already existing, at which you might as well use mtl

Torsten Schmits

yeah, I think it helps to imagine that the stack is only reified very late and everything before that is just negotiation. If you want to disambiguate, you can use tags or newtypes.
but there was one statement by Love in that thread that described how to prevent an effect from being "shared" like you were concerned about, I think that triggered me to post it

Torsten Schmits

The solution is to install a local Error effect

that was it

Georgi Lyubenov // googleson78

The list is indeed reified only when you actually try to run a program, but nothing is stopping me from introducing two identical effects e.g. via raiseUnder2, as far as I recall/can tell

I won't have any ambiguity issues either, if I run them immediately, and furthermore there could easily be another "copy" of the same effects further down the line :thinking:

Georgi Lyubenov // googleson78

(which should be the case for the Transaction thing)

Georgi Lyubenov // googleson78

yeah, they are talking about the same concern

Love Waern (King of the Homeless)

Effect introduction exists precisely so interpreters can avoid interacting with ones application-code may use!
The way sending effects works is that any Member e r constraint always targets the top-most occurrence of e in r; and this is r at the time of sending. No effects introduced to r after-the-fact will ever be targeted.

For example, say you have:

data TellInt m a where
  TellInt :: Int -> TellInt m ()

makeSem ''TellInt

runTellInt :: Sem (TellInt ': r) a -> Sem r (Int, a)
runTellInt =
    runState @Int 0
  . interpret (\case
      TellInt i -> modify' @Int (+i)
    )
  . raiseUnder
-- Note that "interpret h . raiseUnder" is equivalent to "reinterpret h"

main :: (Int, (Int, ())
main = run $ runState @Int 0 $ runTellInt $ modify' @Int (+3) >> tellInt 7

This returns (3, (7, ()), like you'd want.
To explain why this works, the initial effect stack (in which you execute the modify' @Int (+3) and tellInt 7) is
'[TellInt, State Int]. To make this more clear, I'll attach labels to these: call the first effect a, and second effect b:
'[a ~ TellInt, b ~ State Int]. Now, Member targets the top-most effect of the stack; this means that modify' @Int (+3) will target b, and tellInt 7 will target a.

Now, we run runTellInt. This interprets TellInt by introducing a State Int effect of its own under TellInt. Call this newly introduced effect c: the effect stack becomes '[a ~ TellInt, c ~ State Int, b ~ State Int]. This has not changed what effects that have targeted by previous uses of send; only what effects that will be targeted by future effects of send. The put @Int 3 executed earlier still belongs to b.

The interpret inside of runTellInt does some modify's of its own; but these will target c instead of the previously-existing b, since now c is the topmost State Int. Furthermore, the runState executed in runTellInt interprets specifically c; b is left untouched.

Once runTellInt is done, the effect stack will be '[b ~ State Int]. Since runTellInt immediately consumed the State Int effect it introduced, the fact another instance of State Int other than b was ever present in the effect stack is completely invisible. Any State actions from here on out will target b again.

The final runState does interpret b. Because of that, it will interpret the modify' @Int (+3) done in the very beginning, and thus the final state will be 3.

TL;DR: No. Introducing a new effect only affects what sends are done from that point on, and only until the introduced effect is consumed. If an interpreter immediately consumes the effect it introduces (like runTellInt), this makes the fact that an effect was introduced completely invisible to the outside world.