What is the easiest way to convert effect to the same effect in `purescript-run`?

I am trying to create a handler in purescript-run that consumes an effect but uses the same effect. I mean, a handler like the following.

testHandler :: forall r. Run (READER String + r) ~> Run (READER Int + r)
testHandler = interpret (on _reader handler send)
  where
  handler :: Reader String ~> Run (READER Int + r)
  handler = case _ of
    Reader k -> do
      x <- ask
      pure $ k (show x)

This is a handler that converts a READER String effect to a READER Int effect.
However, I get an error on _reader.

Could not match type

    ( reader :: Reader Int
    ...
    | r6
    )

  with type

    r6

What I’m actually trying to do is reimplement the automatic differentiation using Algebraic Effects with Extensible Effects, and not using the READER effect.

I have tried to fix this error but cannot find an easy way. Any suggestions on how to do this?

The following compiles, the Union constraint drops out from what expand expects. It feels a little redundant, but maybe it’s saying something important about r, I haven’t put much thought into it.

testHandler :: forall r t. Union r (READER Int + ()) (READER Int + r) => Run (READER String + r) ~> Run (READER Int + r)
testHandler = interpret (on _reader handler (send <<< expand'))
  where
  expand' :: VariantF r ~> VariantF (READER Int + r)
  expand' = expand
  
  handler :: Reader String ~> Run (READER Int + r)
  handler = case _ of
    Reader k -> do
      x <- ask
      pure $ k (show x)
1 Like

Note that expand is import from variant, not run. If you wanted to use the other, the composition would be in the other direction

This solved the problem! Thank you.

But expandOne :: forall sym r1 r2 f . IsSymbol sym => Cons sym f r1 r2 => Variant r1 -> Variant r2 can’t be created?
Maybe there is something some problem with duplicate field names?

This signature on it’s own isn’t solvable without explicit type applications, but even if it was, it isn’t typesafe given duplicate record labels. Union has a bias where the left side always takes precedence. Meaning, any input rows remain at the top of the stack for a given label. Your expandOne pushes the output to the top of the stack. If I had Variant (foo :: Int), with your signature I could write expandOne @"foo" @String wat :: Variant (foo :: String, foo Int), which just unsafe coerces an Int to String. If you wanted to implement this with Cons, you would need an additional Lacks constraint, much like with Records.

1 Like

This seems really similar to a question I asked awhile ago

Basically with the same answer about Union having a left bias.

1 Like

For this specific example, you can use Data.Functor.Variant.over to translate Reader String to Reader Int within the variant directly:

module HoistRun where

import Prelude

import Data.Functor.Variant (over)
import Run (Run, interpret, send)
import Run.Reader (READER, Reader(..))
import Type.Row (type (+))


testHandler :: forall r. Run (READER String + r) ~> Run (READER Int + r)
testHandler = interpret (send <<< over { reader: handler } identity)
  where
  handler = case _ of
    Reader k -> Reader (k <<< show)

the over function is something I added for this use case, to avoid extra constraints

but the problem is that this doesn’t scale to nontrivial transformations, where you need to access other effects too

I’m trying to think whether there is even any safe API that could allow such a thing at the Variant level that also works for Run … I’m not sure

yeah, I can’t think of one

but Run could probably have a hoist-like function that specializes to what you want here, implemented with an unsafe expand

1 Like

I see, or if Variant r is right biased, it would be safe to use Cons.

Ah! Maybe a version of on that doesn’t change the row would be nice?

forall sym f a b r1 r2. Cons sym f r1 r2 => IsSymbol sym => Proxy sym -> (f a -> b) -> (VariantF r2 a -> b) -> VariantF r2 a -> b

Isn’t this safe?

Yeah, something like that would be possible. One could also add the Union constraint to the callback making it available to a call to expand, or additionally provide some other type equality.