4

I have been playing around with “freer monads” and extensible effects, implemented in the freer-effects package, and I’ve run into a problem that seems feasible but I’m having trouble solving.

I’ve written a type that represents simple interactions with a file system:

data FileSystem v where
  ReadFile :: FilePath -> FileSystem String
  WriteFile :: FilePath -> String -> FileSystem ()

Writing an interpreter for this in IO is easy, but boring. What I’m really interested in doing is writing a pure interpreter that uses State internally. I could effectively inline the implementation of runState into my interpreter for FileSystem, but that seems like it would sort of defeat the purpose. What I’d really like to be able to do is write a transformation between these two types, then reuse the State interpreter.

Writing such a transformation is straightforward:

fsAsState :: forall v r. FileSystem v -> Eff (State [(FilePath, String)] ': r) v
fsAsState (ReadFile a) = (lookup a <$> get) >>=
  maybe (fail "readFile: file does not exist") return
fsAsState (WriteFile a b) = modify $ \fs ->
  (a, b) : filter ((/= a) . fst) fs

Now I want a generic reencode function that can accept my fsAsState transformation and use it to interpret my FileSystem by reusing the State interpreter. With such a function, I would be able to write the following interpreter:

runInMemoryFS :: forall r w. [(FilePath, String)] -> Eff (FileSystem ': r) w -> Eff r (w, [(FilePath, String)])
runInMemoryFS fs m = runState (reencode fsAsState m) fs

The tricky thing is actually implementing reencode. I’ve gotten something that almost typechecks:

reencode :: forall r w f g. (forall v. f v -> Eff (g ': r) v) -> Eff (f ': r) w -> Eff (g ': r) w
reencode f m = loop m
  where
    loop :: Eff (f ': r) w -> Eff (g ': r) w
    loop (Val x) = return x
    loop (E u q) = case decomp u of
      Right x -> qComp q loop =<< f x
      Left u' -> E (weaken u') undefined

Unfortunately, I can’t figure out how to provide the second argument to E in the final case of loop. I don’t think I understand the implementation details of how the optimized FTCQueue type works in order to understand if there’s something I need to do here that is simple, or if what I’m doing is just impossible.

Is this possible? If the answer is no, and it turns out that what I’m doing is, in fact, impossible, I would be interested in an explanation to help me understand why.

Cactus
  • 27,075
  • 9
  • 69
  • 149
Alexis King
  • 43,109
  • 15
  • 131
  • 205

1 Answers1

2

DISCLAIMER: the below typechecks but I haven't tried running it.

You need to walk q (from the E u q pattern match) and shift all its steps from Eff (f ': r) to Eff (g ': r). We can write this traversal polymorphically:

shiftQ :: forall m n a b. (forall a. m a -> n a) -> FTCQueue m a b -> FTCQueue n a b
shiftQ shift q = case tviewl q of
    TOne act -> tsingleton (shift . act)
    act :| q -> go (tsingleton (shift . act)) q
  where
    go :: forall a b c. FTCQueue n a b -> FTCQueue m b c -> FTCQueue n a c
    go q' q = case tviewl q of
        TOne act -> q' |> (shift . act)
        act :| q -> go (q' |> (shift . act)) q

(it's a bit awkward because we can only snoc and only uncons FTCQueues).

and then we can use it in reencode by passing reencode f itself as the shifter:

reencode :: forall r w f g. (forall v. f v -> Eff (g ': r) v) -> Eff (f ': r) w -> Eff (g ': r) w
reencode f m = loop m
  where
    loop :: Eff (f ': r) w -> Eff (g ': r) w
    loop (Val x) = return x
    loop (E u q) = case decomp u of
      Right x -> qComp q loop =<< f x
      Left u' -> E (weaken u') (shiftQ (reencode f) q)
Cactus
  • 27,075
  • 9
  • 69
  • 149
  • 3
    Indeed, this does seem to work, but it’s a lot uglier than I expected. I’m now a little worried—should I take the difficulty of this as an indication that I’m “using freer wrong”? Or is this just the complexity cost we must currently pay for performant extensible effects? – Alexis King Jul 03 '17 at 02:30
  • 1
    I have no idea -- I was just following the types. I haven't used Freer or freer-effects at all. – Cactus Jul 03 '17 at 02:52