raiseUnder and multiple effect interpretation - Polysemy

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

Torsten Schmits

What's the right way to do this:

interp1 :: Member e3 r => Sem (e1 : e2 : r) a -> Sem r a

interp2 :: Sem (e1 : e2 : r) a -> Sem r a
interp2 = interp_e3 . interp1 . raiseUnder???

It seems that I would need to introduce an effect two levels deep, but I don't see a combinator for that. Is there a better way?

Torsten Schmits

to be clear, I want e3 not to be needed in the outside interpreter stack

Torsten Schmits

which works fine when interp1 is a single effect, with raiseUnderN

TheMatten

I guess we should try to get

lift :: forall effs1 effs2 a. Lift effs1 effs2 => Sem effs1 a -> Sem effs2 a

at some point

Torsten Schmits

for your FYI, I made combinators for this problem: https://github.com/polysemy-research/polysemy/pull/369

@TheMatten pls2check!

These combinators are like raiseUnder, but place the new effect at the second and third position in the list. This is for interpreters that consume two effects, delegating to two other interpreters...
TheMatten

@Torsten Schmits Cool, thanks!
While talking about "shuffling" effects around, what about porting lift from eff, which would allow us to reorder and add effects freely?

馃毀 a work in progress effect system for Haskell 馃毀. Contribute to hasura/eff development by creating an account on GitHub.
Torsten Schmits

would love to! you wanna merge this anyway for the time being?

Torsten Schmits

ah, I see you already approved. :rocket:

TheMatten

Even if we added lift later, we probably want these as more "inferable" variants, because they should produce much more sane type errors

TheMatten

We would just turn them into call to lift in that case

Georgi Lyubenov // googleson78

Well this is rather unfortunate! raise2Under actually means "raise an effect under two effects", and the existing raiseUnder2 actually means "raise two effects under the current one" :sweat_smile:

Georgi Lyubenov // googleson78

again with the self-recursive calls where it's entirely non-obvious to me how this is terminating at all -_- (wrt raiseUnder)

TheMatten

That recursive use should mostly matter in higher-order effects, where you call it on your argument to actually conform to "latest" row

TheMatten

So it stops at some point, because you don't have infinitely nested higher-order calls :slight_smile:

TheMatten
-- inspired by
-- https://github.com/hasura/eff/blob/bf0520d0fb0644fef2bb78d0cfa289efdc9fc851/eff/src/Control/Effect/Internal.hs#L688

class Raise (r :: [Effect]) (r' :: [Effect]) where
  raiseUnion :: Union r m a -> Union r' m a

instance {-# overlapping #-} Raise r r where
  raiseUnion = id

instance (r' ~ (_0:r''), Raise r r'') => Raise r r' where
  raiseUnion = (\(Union n w) -> Union (There n) w) . raiseUnion

class Subsume (r :: [Effect]) (r' :: [Effect]) where
  subsumeUnion :: Union r m a -> Union r' m a

instance {-# incoherent #-} Raise r r' => Subsume r r' where
  subsumeUnion = raiseUnion

instance (Member e r', Subsume r r') => Subsume (e:r) r' where
  subsumeUnion = either subsumeUnion injWeaving . decomp

instance Subsume '[] r where
  subsumeUnion = absurdU

raise' :: Subsume r r' => Sem r a -> Sem r' a
raise' = hoistSem $ hoist raise' . subsumeUnion

raise :: Sem r a -> Sem (e ': r) a
raise = raise'

raiseUnder :: Sem (e1 ': r) a -> Sem (e1 ': (e2 ': r)) a
raiseUnder = raise'

raiseUnder2 :: Sem (e1 ': r) a -> Sem (e1 ': (e2 ': (e3 ': r))) a
raiseUnder2 = raise'

raiseUnder3 :: Sem (e1 ': r) a -> Sem (e1 ': (e2 ': (e3 ': (e4 ': r)))) a
raiseUnder3 = raise'

subsume :: Member e r => Sem (e ': r) a -> Sem r a
subsume = raise'
TheMatten

A little bit of incoherence and it can work with both concrete stacks and "polymorphic tails"

TheMatten

Should I open a PR? Or do you have some ideas for names? (those operators in eff are nice)

TheMatten

Maybe something like

-------------------------------------------------------------------------------
-- | Raises effect stack by arbitrary number of effects.
raise' :: Raise r r' => Sem r a -> Sem r' a
raise' = hoistSem $ hoist raise' . raiseUnion

raise :: Sem r a -> Sem (e ': r) a
raise = raise'

raiseUnder :: Sem (e1 ': r) a -> Sem (e1 ': (e2 ': r)) a
raiseUnder = subsume'

raiseUnder2 :: Sem (e1 ': r) a -> Sem (e1 ': (e2 ': (e3 ': r))) a
raiseUnder2 = subsume'

raiseUnder3 :: Sem (e1 ': r) a -> Sem (e1 ': (e2 ': (e3 ': (e4 ': r)))) a
raiseUnder3 = subsume'

class Raise (r :: [Effect]) (r' :: [Effect]) where
  raiseUnion :: Union r m a -> Union r' m a

instance {-# overlapping #-} Raise r r where
  raiseUnion = id

instance (r' ~ (_0:r''), Raise r r'') => Raise r r' where
  raiseUnion = (\(Union n w) -> Union (There n) w) . raiseUnion

-------------------------------------------------------------------------------
-- | Allows reordering and adding known effects on top of effect stack, as long
-- as polymorphic tail of new stack is 'raise'-d version of the original one.
subsume' :: Subsume r r' => Sem r a -> Sem r' a
subsume' = hoistSem $ hoist subsume' . subsumeUnion

subsume :: Member e r => Sem (e ': r) a -> Sem r a
subsume = subsume'

class Subsume (r :: [Effect]) (r' :: [Effect]) where
  subsumeUnion :: Union r m a -> Union r' m a

instance {-# incoherent #-} Raise r r' => Subsume r r' where
  subsumeUnion = raiseUnion

instance (Member e r', Subsume r r') => Subsume (e:r) r' where
  subsumeUnion = either subsumeUnion injWeaving . decomp

instance Subsume '[] r where
  subsumeUnion = absurdU

where names could be switched to raise and raiseN (for N in [1..3]) in v2

Torsten Schmits

TheMatten said:

(Haha, I wonder how many people used https://hackage.haskell.org/package/polysemy-zoo-0.7.0.0/docs/Polysemy-Operators.html :big_smile: )

ooh, never seen those. not sure I find them very readable though, but I'd have to use them a bit to get an impression

Torsten Schmits

I say make a PR and wait for comments over there