That's its intended use, actually. I findsendBundle too clumsy to work with in practice. Intended use is to newtypeBundle and then use rewrite/transform together with injBundle and runBundle to create actions/interpreters on the newtype.

newtypeAppioma=App(Bundle'[Input i, Output o]ma)runApp::Semri->(o->Semr())->Sem(Appio':r)a->SemrarunApponIonO=runOutputSemonO.runInputSem(raiseonI).runBundle.rewrite(\(Appbundle)->bundle)ping::forallior.Member(Appio)r=>o->Semr()pingo=transform(App@i@o.injBundle@(Outputo))(outputo)pong::forallior.Member(Appio)r=>Semripong=transform(App@i@o.injBundle@(Inputi))inputpingpong::forallior.Member(Appio)r=>o->Semripingpongo=dotransform(App@i@o.injBundle@(Outputo))(outputo)transform(App@i@o.injBundle@(Inputi))input

This also shows how you have the choice of between providing direct corresponding actions of the bundled effects (like ping and pong), or choosing only to expose certain combinations of these (like pingpong), similar to how you may restrictively expose power with normal newtypes.

All these ugly type applications arise as an effect of App being polymorphic (in i and o). You don't need them if your effect newtype is monomorphic, or if you use polysemy-plugin.

Effect newtypes aren't that special if you're only using them to represent first-order effects, since re/interpret and friends is a just as nice or nicer way to do the same job. They become more interesting if you want to build up a higher-order effect because, y'know, it allows you to avoid having to deal with interpretH.

dataFunctionabmcwhereApply::a->FunctionabmbmakeSem''FunctionrunPureFunction::(a->b)->InterpreterFor(Functionab)rrunPureFunctionf=interpret$\caseApplyx->pure$fxrunEffectfulFunction::(a->Semrb)->InterpreterFor(Functionab)rrunEffectfulFunctionf=interpret$\caseApplyx->fxrunCompose::forallabcrd.Member(Functionbc)(Functionab':Functionbc':r)=>(Sem(Functionbc':r)d->Semrd)->(Sem(Functionab':Functionbc':r)d->Sem(Functionbc':r)d)->Sem(Functionac':r)d->SemrdrunComposebcab=bc.ab.reinterpret2(\caseApplyx->apply@a@bx>>=apply@b@c)newtypeBoomerangabmc=Boomerang{unBoomerang::Bundle'[Function a b, Function b a]mc}runBoomerang::forallabrc.(Sem(Functionab':Functionba':r)c->Sem(Functionba':r)c)->(Sem(Functionba':r)c->Semrc)->Sem(Boomerangab':r)c->SemrcrunBoomerangabba=ba.ab.runBundle.rewriteunBoomerangforward::forallabr.Member(Boomerangab)r=>a->Semrbforwardx=transform(Boomerang@a@b.injBundle@(Functionab))(apply@a@bx)reverse::forallabr.Member(Boomerangab)r=>b->Semrareversex=transform(Boomerang@a@b.injBundle@(Functionba))(apply@b@ax)

Everything works up until reverse (although the type signatures of runCompose and runBoomerang are strange), but reverse gives me this compiler error: Could not deduce: a ~ b arising from a use of ‘injBundle’.

Ha! You just found one of the few cases where Member inference breaks even with type applications! Basically, Haskell can't tell that Function a b IS NOT Function b a, so the send is ambiguous (oversimplifying a bit; the reason forward works but reverse doesn't despite forward has the same ambiguity issue is due to how Member works.)

Solution is to import Polysemy.Membership and write this:

Interpreters that take interpreters as arguments often have this problem. If you're always gonna postcompose ab and bc, I recommend removing them as arguments to runCompose:

-- | An effect for calling functions.---- Examples:---- >>> (apply 1 >>= apply @Int @String >>= embed . putStrLn)-- & runPureFunction show-- & runPureFunction (+1)-- & runM-- 2---- >>> (apply 1 >>= embed . putStrLn)-- & subsumeCompose @Int @Int @String-- & runPureFunction show-- & runPureFunction (+1)-- & runM-- 2dataFunctionabmcwhereApply::a->FunctionabmbmakeSem''Function-- | Run this 'Function' as a pure function.runPureFunction::(a->b)->InterpreterFor(Functionab)rrunPureFunctionf=interpret$\caseApplyx->pure$fx-- | Run this 'Function' as a function with effects.runEffectfulFunction::(a->Semrb)->InterpreterFor(Functionab)rrunEffectfulFunctionf=interpret$\caseApplyx->fx-- | Run this 'Function' in terms of two other 'Function's which compose.-- The other function interpreters must come immediately after this.-- Note that any of the functions may or may not have side effects.runCompose::forallabcrd.Sem(Functionac':r)d->Sem(Functionab':Functionbc':r)drunCompose=reinterpret2$\caseApplyx->apply@a@bx>>=raise.apply@b@c-- | Run this 'Function' in terms of two other 'Function's which compose.-- The other function interpreters may be anywhere in the remaining effect stack.-- Note that any of the functions may or may not have side effects.subsumeCompose::forallabcrd.Members'[ Function a b , Function b c ]r=>InterpreterFor(Functionac)rsubsumeCompose=interpret$\caseApplyx->apply@a@bx>>=apply@b@c

And I have a Boomerang effect:

-- | An effect for wrapping pairs of invertible functions.newtypeBoomerangabmc=Boomerang{unBoomerang::Bundle'[Function a b, Function b a]mc}forward::Member(Boomerangab)r=>a->Semrbforward=transform(Boomerang.injBundle).applybackward::Member(Boomerangab)r=>b->Semrabackward=transform(Boomerang.Bundle(ThereHere)).apply-- | Run in terms of two 'Function's, either of which may have effects.-- The functions should be provided next.runBoomerang::Sem(Boomerangab':r)c->Sem(Functionab':Functionba':r)crunBoomerang=runBundle.rewriteunBoomerang-- | Run in terms of an isomorphism.runBoomerangIso::Iso'ab->Sem(Boomerangab':r)c->SemrcrunBoomerangIsol=runPureFunction(view$froml).runPureFunction(viewl).runBoomerang-- | Run in terms of two 'Function's, either of which may have effects.-- These functions can be anywhere in the effect stack, so can be reused for multiple 'Boomerang's.subsumeBoomerang::Members'[Function a b, Function b a]r=>InterpreterFor(Boomerangab)rsubsumeBoomerang=subsumeBundle.rewriteunBoomerang-- | Run this 'Boomerang' in terms of two other 'Boomerang's which compose (in both directions!)-- The other boomerangs must be run immediately after this.runComposeBoomerang::forallabcdr.Sem(Boomerangac':r)d->Sem(Boomerangab':Boomerangbc':r)drunComposeBoomerang=subsumeComposeBoomerang@a@b@c.raiseUnder2-- | Run this 'Boomerang' in terms of two other 'Boomerang's which compose (in both directions!)-- The other 'Boomerang's may appear anywhere in the effect stack.subsumeComposeBoomerang::forallabcr.Members'[Boomerang a b, Boomerang b c]r=>InterpreterFor(Boomerangac)rsubsumeComposeBoomerang=runEffectfulFunction(backward@b@c>=>backward@a@b).runEffectfulFunction(forward@a@b>=>forward@b@c).runBundle.rewriteunBoomerang

But that runComposeBoomerang doesn't compile. The error is similar to before, @Love Waern (King of the Homeless) , Couldn't match type ‘b’ with ‘a’ arising from a use of ‘subsumeComposeBoomerang’. But I can't see a way around it this time :/

I don't think there is one! subsumeComposeBoomerang has the problem I mentioned above: Haskell can't distinguish Function a b and Function b a, and thus when trying to resolve(Member (Boomerang a b) r, Member (Boomerang b a) r), it will attempt to unify a and b. Above, I fixed this by either raiseing or throwing in explicit membership proofs, but that trick only works when the effect stack r is at least partly known. In subsumeComposeBoomerang, ris completely polymorphic; you only have Member to work with, and in this case, it just doesn't work.

There are two ways to solve this. One way is to distinguish between the two effects by wrapping one in a newtype, and unwrapping them later. Tagged can be used for this purpose.

What I recommend instead is that you inline the definition ofsubsumeComposeBoomerangin runComposeBoomerang. That way, you can do the raise trick again.

Ah yes, that's why subsumeComposeBoomerang requires AllowAmbiguousTypes to compile. It's the same with subsumeCompose. The latter works though, once you give it concrete types to work with.

It distinguishes between e.g. Function Int Float and Function Float Int of course, but with polymorphic Function a b compiler can't be sure that a ~ b doesn't appear somewhere in the program

Can anyone give me an example of using

`Bundle`

? I can't even work out how to make a newtype using it :(https://funprog.zulipchat.com/#narrow/stream/216942-Polysemy/topic/wrap.20higher-order.20effect/near/190317850

I did some live coding on here figuring it out :laughing:

Ah, so it isn't used with newtype at all? I was imagining something along the lines of

well I guess you

cando that, but it's not necessaryThat's its intended use, actually. I find

`sendBundle`

too clumsy to work with in practice. Intended use is to`newtype`

`Bundle`

and then use`rewrite`

/`transform`

together with`injBundle`

and`runBundle`

to create actions/interpreters on the newtype.Here's an example, by using your

`App`

:This also shows how you have the choice of between providing direct corresponding actions of the bundled effects (like

`ping`

and`pong`

), or choosing only to expose certain combinations of these (like`pingpong`

), similar to how you may restrictively expose power with normal newtypes.All these ugly type applications arise as an effect of

`App`

being polymorphic (in`i`

and`o`

). You don't need them if your effect newtype is monomorphic,orif you use`polysemy-plugin`

.Effect newtypes aren't that special if you're only using them to represent first-order effects, since

`re/interpret`

and friends is a just as nice or nicer way to do the same job. They become more interesting if you want to build up a higher-order effect because, y'know, it allows you to avoid having to deal with`interpretH`

.A very neat example I just thought of is a

`Mask`

effect that relies on`Final IO`

:Although this doesn't need

`Bundle`

.awesome

Thanks @Love Waern (King of the Homeless), that example is very helpful!

I'm having trouble getting this thing to work:

Everything works up until

`reverse`

(although the type signatures of`runCompose`

and`runBoomerang`

are strange), but`reverse`

gives me this compiler error:`Could not deduce: a ~ b arising from a use of ‘injBundle’`

.(eventually I want to be able to compose Boomerangs, so that I can build a decode/encode chain out of a mixture of pure and effectful functions)

I think I need a function of type

`(Sem (e1 ': r) a -> Sem r a) -> Sem (e1 ': e2 ': r) a -> Sem (e2 ': r) a`

Ha! You just found one of the few cases where

`Member`

inference breaks even with type applications! Basically, Haskell can't tell that`Function a b`

IS NOT`Function b a`

, so the send is ambiguous (oversimplifying a bit; the reason`forward`

works but`reverse`

doesn't despite`forward`

has the same ambiguity issue is due to how`Member`

works.)Solution is to import

`Polysemy.Membership`

and write this:That should fix it.

I think. Haven't checked.

Yes, that fixes it. Thanks :)

What about the strange types of

`runCompose`

and`runBoomerang`

? Is there a way to simplify these?For

`runBoomerang`

, unfortunately not really. For`runCompose`

, only a tiny bit:Interpreters that take interpreters as arguments often have this problem. If you're always gonna postcompose

`ab`

and`bc`

, I recommend removing them as arguments to`runCompose`

:And then apply

`ab`

and`bc`

through simple composition.And of course, the same thing can be done for

`runBoomerang`

:Ok, I'll work with that :)

Ok, I'm nearly there. I have a

`Function`

effect:And I have a

`Boomerang`

effect:But that

`runComposeBoomerang`

doesn't compile. The error is similar to before, @Love Waern (King of the Homeless) ,`Couldn't match type ‘b’ with ‘a’ arising from a use of ‘subsumeComposeBoomerang’`

. But I can't see a way around it this time :/I don't think there is one!

`subsumeComposeBoomerang`

has the problem I mentioned above: Haskell can't distinguish`Function a b`

and`Function b a`

, and thus when trying to resolve`(Member (Boomerang a b) r, Member (Boomerang b a) r)`

, it will attempt to unify`a`

and`b`

. Above, I fixed this by either`raise`

ing or throwing in explicit membership proofs, but that trick only works when the effect stack`r`

is at least partly known. In`subsumeComposeBoomerang`

,`r`

is completely polymorphic; you only have`Member`

to work with, and in this case, it just doesn't work.There are two ways to solve this. One way is to distinguish between the two effects by wrapping one in a newtype, and unwrapping them later.

`Tagged`

can be used for this purpose.What I recommend instead is that you inline the definition of

`subsumeComposeBoomerang`

in`runComposeBoomerang`

. That way, you can do the`raise`

trick again.See if that works.

Ah yes, that's why

`subsumeComposeBoomerang`

requires`AllowAmbiguousTypes`

to compile. It's the same with`subsumeCompose`

. The latter works though, once you give it concrete types to work with.Yes, your

`runComposeBoomerang`

works :)It took me a while to work out how the

`raise`

trick works, but I think I have it now.I don't see why Haskell can't distinguish between

`Function a b`

and`Function b a`

though, at least when using`ScopedTypeVariables`

and`TypeApplications`

.It distinguishes between e.g.

`Function Int Float`

and`Function Float Int`

of course, but with polymorphic`Function a b`

compiler can't be sure that`a ~ b`

doesn't appear somewhere in the programOh, I think I see, because it can't exclude that possibility, it can't exclude using the first effect on the stack.