If I use raise to introduce an A into my effects stack, will how I dispatch thatA affect any otherAs 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...
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?
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:
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 ...
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
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:
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 rat the time of sending. No effects introduced to r after-the-fact will ever be targeted.
For example, say you have:
dataTellIntmawhereTellInt::Int->TellIntm()makeSem''TellIntrunTellInt::Sem(TellInt':r)a->Semr(Int,a)runTellInt=runState@Int0.interpret(\caseTellInti->modify'@Int(+i)).raiseUnder-- Note that "interpret h . raiseUnder" is equivalent to "reinterpret h"main::(Int,(Int,())main=run$runState@Int0$runTellInt$modify'@Int(+3)>>tellInt7
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.
If I use
raise
to introduce anA
into my effects stack, will how I dispatch thatA
affect any otherA
s already present in the stack? (inspiration for this question or maybe I'm just not understanding what's going on there)I think that
raise
is just there to line up the types, because of the possibility to switch interpreters mid-interpretationbut how is the started transaction passed in from there supposed to get used for the interpretation for the leftover
Database
effect?so you're talking about the
raiseUnder2
, not theraise
, rightmy guess would be that the other
Database
will be consumed by another interpreter that is basically not doing anythingto me it seems the opposite - this one does nothing, and the other one that is not seen here does the work
but the op did say transactions are working..
Georgi Lyubenov // googleson78 said:
I wanted to write the same thing to the letter :joy:
so that's where my original question came from - there must be something about effect interactions/semantics that I'm missing
it looks like op is guessing that
Database
is in the stack of the higher order thunk inTransaction
. so if it is, does it get executed inbracket
? is that the question we're having?even if you guess right, raise introduces new effects, no?
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: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:
I'm following that, but I think that the instance of my question is not directly related to what they're discussing (I think!*)
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
it's probably buried in there somewhere!
I think this (my original question) would just be "unsound" in some sense of the word, if it did work like this
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
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
that was it
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 tellI 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:
(which should be the case for the
Transaction
thing)yeah, they are talking about the same concern
or at least it's mentioned
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 ofe
inr
; and this isr
at the time of sending. No effects introduced tor
after-the-fact will ever be targeted.For example, say you have:
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)
andtellInt 7
) is'[TellInt, State Int]
. To make this more clear, I'll attach labels to these: call the first effecta
, and second effectb
:'[a ~ TellInt, b ~ State Int]
. Now,Member
targets the top-most effect of the stack; this means thatmodify' @Int (+3)
will targetb
, andtellInt 7
will targeta
.Now, we run
runTellInt
. This interpretsTellInt
by introducing aState Int
effect of its own underTellInt
. Call this newly introduced effectc
: the effect stack becomes'[a ~ TellInt, c ~ State Int, b ~ State Int]
. This has not changed what effects that have targeted by previous uses ofsend
; only what effects that will be targeted by future effects ofsend
. Theput @Int 3
executed earlier still belongs tob
.The
interpret
inside ofrunTellInt
does somemodify'
s of its own; but these will targetc
instead of the previously-existingb
, since nowc
is the topmostState Int
. Furthermore, therunState
executed inrunTellInt
interprets specificallyc
;b
is left untouched.Once
runTellInt
is done, the effect stack will be'[b ~ State Int]
. SincerunTellInt
immediately consumed theState Int
effect it introduced, the fact another instance ofState Int
other thanb
was ever present in the effect stack is completely invisible. AnyState
actions from here on out will targetb
again.The final
runState
does interpretb
. Because of that, it will interpret themodify' @Int (+3)
done in the very beginning, and thus the final state will be3
.TL;DR: No. Introducing a new effect only affects what
send
s are done from that point on, and only until the introduced effect is consumed. If an interpreter immediately consumes the effect it introduces (likerunTellInt
), this makes the fact that an effect was introduced completely invisible to the outside world.