lexi-lambda / freer-simple

A friendly effect system for Haskell
https://hackage.haskell.org/package/freer-simple
BSD 3-Clause "New" or "Revised" License
227 stars 19 forks source link

Problem with introducing new intermediate states #2

Closed jwiegley closed 6 years ago

jwiegley commented 6 years ago

Something that I used to do a lot of with free-effects was this:

{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE PackageImports #-}

module Bug where

import "freer-simple" Control.Monad.Freer
import "freer-simple" Control.Monad.Freer.State

foo :: Member IO r => Eff r Int
foo = execState 0 $ do
    put 10

{-
• Could not deduce (freer-simple-1.0.0.0:Data.OpenUnion.Internal.FindElem
                      (State Integer) r (State Int : r))
    arising from a use of ‘put’
  from the context: Member IO r
    bound by the type signature for:
               foo :: forall (r :: [* -> *]). Member IO r => Eff r Int
    at /Users/johnw/dl/Bug.hs:9:1-31
• In a stmt of a 'do' block: put 10
  In the second argument of ‘($)’, namely ‘do put 10’
  In the expression: execState 0 $ do put 10
-}

However, with freer-simple this same code gives me type errors that I've yet to work around. Surely I'm just missing something obvious?

jwiegley commented 6 years ago

Never mind that example, with a type annotation it worked. The real code where I'm running into a problem is:

analyzeProgram
    :: forall r. ( Member (Reader Options) r
      , Member Printer r
      , Member FileReader r )
    => Map String (ProgramInfo, Eff '[Inst] ())
    -> Eff r Program
analyzeProgram pgms
    = evalState (M.empty :: TaskMap)
    . evalState (IM.empty :: VarRefs)
    . evalState (newGraph :: ProgramGraph) $ go
  where
    go = do
        (mstate, tasks) <- readTasks

        infos <- forM (M.assocs pgms) $ \(name, (info, code)) ->
            info <$ buildGraph tasks name code

        Program <$> pure infos
                <*> get @ProgramGraph
                <*> pure mstate
                <*> pure tasks
                <*> ask @Options
jwiegley commented 6 years ago

With error:

• Could not deduce (Data.OpenUnion.Internal.FindElem
                      (Reader Options)
                      r
                      (State ProgramGraph : State VarRefs : State TaskMap : r))
    arising from a use of ‘readTasks’
  from the context: (Member (Reader Options) r, Member Printer r,
                     Member FileReader r)
    bound by the type signature for:
               analyzeProgram :: forall (r :: [* -> *]).
                                 (Member (Reader Options) r, Member Printer r,
                                  Member FileReader r) =>
                                 Map String (ProgramInfo, Eff '[Inst] ()) -> Eff r Program
    at /Users/johnw/bae/concerto/solver/src/Concerto/Program.hs:(46,1)-(51,20)
• In a stmt of a 'do' block: (mstate, tasks) <- readTasks
  In the expression:
    do (mstate, tasks) <- readTasks
       infos <- forM (M.assocs pgms)
                  $ \ (name, (info, code)) -> info <$ buildGraph tasks name code
       Program <$> pure infos <*> get @ProgramGraph <*> pure mstate
         <*> pure tasks
         <*> ask @Options
  In an equation for ‘go’:
      go
        = do (mstate, tasks) <- readTasks
             infos <- forM (M.assocs pgms)
                        $ \ (name, (info, code)) -> info <$ buildGraph tasks name code
             Program <$> pure infos <*> get @ProgramGraph <*> pure mstate
               <*> pure tasks
               <*> ask @Options
   |
jwiegley commented 6 years ago

Explicit uses of raise cures this problem, but wasn't needed with freer-effects.

lexi-lambda commented 6 years ago

Could you please give a minimal program that reproduces the problem? Specifically, something that compiles with freer-effects but not freer-simple?

jwiegley commented 6 years ago

I ended up rewriting my code to avoid this problem, so rather than tracking it down, I'll just close this issue until it bites me again and forces me to create that example. :)

hurryabit commented 6 years ago

I've recently had the same problem. A small example which compiles with freer-effects but doesn't with freer-simple unless you add an explicit raise is as follows:

f :: Member (Error String) effs => Eff (Reader () : effs) ()
f = throwError "bam!"

The error message is:

    • Could not deduce (freer-simple-1.0.0.0:Data.OpenUnion.Internal.FindElem
                          (Error [Char]) effs (Reader () : effs))
        arising from a use of ‘throwError’
      from the context: Member (Error String) effs
        bound by the type signature for:
                   f :: forall (effs :: [* -> *]).
                        Member (Error String) effs =>
                        Eff (Reader () : effs) ()
        at /Users/Martin/GitHub/freer-test/src/Simple.hs:16:1-60
    • In the expression: throwError "bam!"
      In an equation for ‘f’: f = throwError "bam!"

If I'm not mistaken, this problem is caused by the introduction of the third parameter to the FindElem type class in Data.OpenUnion.Internal.

jwiegley commented 6 years ago

There you go, @hurryabit has run into the same problem as me, with a nice example even.

lexi-lambda commented 6 years ago

This is tricky. In hindsight, the problem is obvious, and indeed it happens due to the extra FindElem parameter, but I don’t know how to fix it without giving up on the custom type error. I’m going to keep thinking about it, but if I can’t come up with a fix, I’ll have to just (sadly) remove the custom type error code.

lexi-lambda commented 6 years ago

Fixed in v1.0.1.1.