Skip to content

Combinators to combat excessive indentation #42

@jeukshi

Description

@jeukshi

I've been thinking that it could be nice to have combinators that can turn this fin of doom.

f = runEff \io -> do
    evalState 1 \s -> do
        withFile io "file.txt" ReadMode \f -> do
            withConnection io url \c -> do
                undefined

Into this beauty.

f = runEff \io -> do
    withEff3 (evalState 1) (withFile io "file.txt" ReadMode) (withConnection io url) \s f c -> do
        undefined
-- or
f = runEff \io -> do
    withEff3
        (evalState 1)
        (withFile io "file.txt" ReadMode)
        (withConnection io url)
        \s f c -> do
            undefined

Can it be done? Well, apparently not by me! My haskell-fu is too weak, and so is my intuition about those type tags.

Naive approach.

withEff2
    :: forall f1 e1 f2 e2 es r
     . ((f1 e1 -> Eff (e1 :& es) r) -> Eff es r)
    -> ((f2 e2 -> Eff (e2 :& e1 :& es) r) -> Eff es r) -- This is cheating with `e1 :&`, but whatever, we have other problems for now.
    -> ( f1 e1
         -> f2 e2
         -> Eff (e2 :& e1 :& es) r
       )
    -> Eff es r
withEff2 f1 f2 f = f1 \a1 -> do
    useImpl $ f2 \a2 -> do f a1 a2

Problem is, I can't call this function with any handler, due to missing forall.

Couldn't match type: forall (e :: Effects).
                           State Int e -> Eff (e :& es) a
                     with: f10 e10 -> Eff (e10 :& es) a
      Expected: (f10 e10 -> Eff (e10 :& es) a) -> Eff es a
        Actual: (forall (e :: Effects). State Int e -> Eff (e :& es) a) -> Eff es a

With forall.

withEff2
    :: ((forall e1. f1 e1 -> Eff (e1 :& es) r) -> Eff es r)
    -> ((forall e2. f2 e2 -> Eff (e2 :& e1 :& es) r) -> Eff es r) -- Cheating.
    -> ( forall e3 e4
          . f1 e3
         -> f2 e4
         -> Eff (e4 :& e3 :& es) r
       )
    -> Eff es r
withEff2 f1 f2 f = f1 \a1 -> do
    useImpl $ f2 \a2 -> do f (unsafeCoerce a1) a2

But now I can't convince GHC that those es are the same.

And there is this :& e1 problem, that doesn't let me call this function either.

So I guess I want this:

withEff2
    :: ((forall e1. f1 e1 -> Eff (e1 :& es) r) -> Eff es r)
    -> ((forall e2. f2 e2 -> Eff (e2 :& es) r) -> Eff es r)
    -> ( forall e3 e4
          . f1 e3
         -> f2 e4
         -> Eff (e4 :& e3 :& es) r
       )
    -> Eff es r
withEff2 f1 f2 f = f1 \a1 -> do
    useImpl $ f2 \a2 -> do unsafeCoerce f a1 a2

Which I can call, and works like a charm.

g = runEff \io -> do
    withEff2 (evalState (0 :: Int)) (withFile io "file.txt" ReadMode) \a b -> do
        put a 2
        hIsEOF b

The last problem is that this doesn't work with less polymorphic handlers like try or runState, but I can live with that.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions