8

So we have the free monad: (encoding may vary, but they're all the same)

data Free f a = Pure a
              | Impure (f (Free f a))

instance Functor f => Monad (Free f) where
    pure = Pure
    Pure   x >>= f = f x
    Impure x >>= f = Impure ((>>= f) <$> x)

liftFree :: Functor f => f a -> Free f a
liftFree x = Impure (Pure <$> x)

runFree :: Monad g => (forall x. f x -> g x) -> Free f a -> g a
runFree _ (Pure   x) = pure x
runFree f (Impure x) = join (runFree f <$> f x)

such that runFree forms a monad homomorphism, which is the defining property of the free monad.

runFree f (pure x) = pure x
runFree f (liftFree x >>= liftFree . g) = f x >>= (f . g)
-- + some other associativity requirements

We can also make a similar construction to (what I believe to be) the free Bind from semigroupoids, which is a functor with only bind >>-:

data Free1 f a = Done (f a)
               | More (f (Free1 f a))

instance Functor f => Bind (Free f) where
    Done x >>- f = More (f <$> x)
    More x >>- f = More ((>>- f) <$> x)

liftFree1 :: f a -> Free1 f a
liftFree1 = Done

runFree1 :: Bind g => (forall x. f x -> g x) -> Free1 f a -> g a
runFree1 f (Done x) = f x
runFree1 f (More x) = f x >>- runFree1 f

and we get the appropriate bind homomorphism:

such that runFree1 forms a bind homomorphism, which is the defining property:

runFree1 f (liftFree1 x >>- liftFree1 . g) = f x >>- (f . g)
-- + some associativity laws

Now, these two types are great. We can convert from Free1 to Free, which makes sense:

toFree :: Free1 f a -> Free f a
toFree (Done x) = Impure (Pure   <$> x)
toFree (More x) = Impure (toFree <$> x)

but converting backwards is trickier. To go from a Free to a Free1, we would have to handle two cases:

  1. The Free is pure, so cannot be represented in Free1.
  2. The Free is impure, so can be represented in Free1.

It makes sense that these two cases can be handled statically, since we can just match on Pure or Impure.

So a reasonable type signature might be:

fromFree :: Functor f => Free f a -> Either a (Free1 f a)

but I am having problems writing this.

fromFree :: Free f a -> Either a (Free1 f a)
fromFree (Pure   x) = Left x   -- easy
fromFree (Impure x) = Right ?? -- a bit harder

It looks like the main problem is that we need to decide whether or not to use the Done or the More constructor without ever "running" the f. We need a:

f (Free f a) -> Free1 f a

which sounds like it might be troublesome for functors were you can't "get out", like IO.

So, this sounds impossible, unless I'm missing something.

There's another encoding that I've tried:

data Free1 f a = Free1 (f (Free f a))

this does lets us define fromFree, and it borrows from the NonEmpty construction (data NonEmpty a = a :| [a]). And I was able to use this approach when defining the "free Apply", which was nice. This does let us write toFree, fromFree, liftFree1, and a Bind instances. However, I can't seem to write runFree1:

runFree1 :: Bind g => (forall x. f x -> g x) -> f (Free f a) -> g a

as soon as I do anything, I seem to require return :: a -> g a, but we don't have this for all Bind g (I found a possible version that typechecks, but it performs the effects twice and so is not a proper bind homomorphism)

So, while this method gives us fromFree, I can't seem to find a way to write runFree1, which is the very thing that gives it "free Bind" capabilities.

Of these two methods, either we:

  1. Have an actual free Bind with runFree1, but it is imcompatible with Free in that you can't "match" a Free into either a Free1, or a pure value.
  2. Have a type that is compatible with Free (maybe a "nonempty Free"), but is not actually a free Bind (no runFree1), and defeats the whole purpose.

From this I can make one of two conclusions:

  1. There is some way to make a free Bind Free1 that is compatible with Free, but I have not been able to figure it out yet
  2. Fundamentally, we cannot have a free Bind that is compatible with Free. This seems to contradict my intuition (we can always immediately decide if a Free is pure or impure, so we should also be able to immediately distinguish between pure (zero effects) or Free1), but maybe there is a deeper reason that I am missing out on?

Which of these is the case? If #1, what is way, and if #2, what is the deeper reason? :)

Thank you in advance!


Edit To dispel my uncertainty about whether or not I was working with a "true Free Bind", I started looking at one that was truly a Free Bind by definition:

newtype Free1 f a = Free1 { runFree1 :: forall g. Bind g => (forall x. f x -> g x) -> g a }

And I can't seem to be able to write fromFree for this one, either. In the end I seem to need a g (Either a (Free1 a)) -> g a.

If I can't write fromFree for this, then it stands to reason that I can't write fromFree for any implementation of the free bind, since all implementations are isomorphic to this one.

Is there a way to write fromFree for this one, even? Or is it all impossible :'( It all worked so well for Alt/Plus and Apply/Applicative.

Justin L.
  • 13,510
  • 5
  • 48
  • 83
  • 2
    This doesn't seem possible at all. Conider `Free (Integer ->)` and `Free1 (Integer ->)`. – n. m. could be an AI Jun 04 '19 at 05:23
  • @n.m. do you think that is a limitation of this particular encoding of `Free1`, or of all "free `Bind`" implementations in general? (which would imply that my `Free1` is not a proper free Bind) – Justin L. Jun 04 '19 at 05:38
  • Hmm I don't really know... – n. m. could be an AI Jun 04 '19 at 06:28
  • I tried playing around with the "final form" of the Free Bind, which I am sure 100% is the actual Free Bind (`type FreeBind f a = forall g. Bind g => (forall x. f x -> g x) -> g a`) and satisfies all the free bind laws. In this version I cannot implement `fromFree` either, so it might be the case that this is true for all Free Binds. – Justin L. Jun 04 '19 at 06:31
  • If it is true that there the free bind is fundamentally incompatible with the free monad, I'm now curious to see why this is the case for `Bind` and `Monad`, but not for `Apply` and `Applicative`, or `Alt` and `Plus`. Both of these can be properly converted, but not `Bind` and `Monad`. – Justin L. Jun 04 '19 at 06:41
  • I'm now wondering about the `Apply` case. Shouldn't we run into the same problem when it comes to squeezing `Pure` into `data Ap1 f a = Lift (f a) | forall x. Ap (f x) (Ap f (x -> a))`? (Or did I mess up my free apply?) – duplode Jun 08 '19 at 04:49
  • @duplode my Free Apply was `data Ap1 f a = forall x. Ap1 (f x) (Ap f (x -> a))` :) – Justin L. Jun 09 '19 at 21:53
  • @JustinL. (1) Oops, there was a typo in my `Ap1` above: I meant `data Ap1 f a = Lift (f a) | forall x. Ap1 (f x) (Ap1 f (x -> a))`, as I didn't actually use the free applicative `Ap` (In any case, it seems our types are equivalent). (2) I might be missing something, but I still don't see how we might have the `a -> Ap1 f a` injection needed for handling `Pure`, even with your `Ap1` -- the closest thing seems to be `\a -> Ap1 u (Pure (const a))`, but in that case I don't know where to get an `u :: f x` from. – duplode Jun 09 '19 at 22:24
  • @duplode i've written the implementations of the analogous functions here https://github.com/mstksg/functor-combinators/blob/88b1edba8bb30acd37d9b30c0ff63fd9f8725581/src/Data/Functor/Apply/Free.hs#L80-L83 :) – Justin L. Jun 10 '19 at 03:28
  • @JustinL. Thanks! Nevermind my objections -- I had lost track of your actual problem statement for a moment there :) – duplode Jun 10 '19 at 11:44

1 Answers1

5

While Free f a is the type of trees with "f-nodes" and a-leaves, the "free Bind-structure" Free1 f a is the type of such trees with an additional restriction: the children of an f-node are either all leaves or all f-nodes. Thus if we consider binary trees:

data Bin x = Bin x x

Then Free Bin a contains the following tree shape, but Free1 Bin a does not:

Impure (Bin
  (Pure a)
  (Impure (Bin (Pure a) (Pure a))))

because the root node has one leaf and one Bin node as children, while a Free1 Bin a should have either two leaves, or two Bin nodes. Such a pattern could occur deep in a Free tree, so even a partial conversion Free f a -> Maybe (Free1 f a) is not possible with only a Functor f constraint. A Traversable f constraint with an assumption that the traversals are finite makes that conversion possible, but of course it's still not practical for large trees since they must be fully traversed before any output can be produced.

Note that because of the above characterization of Free1, this other definition in terms of Free is not actually equivalent:

data Free1 f a = Free1 (f (Free f a))  -- Not a free Bind
Li-yao Xia
  • 31,896
  • 2
  • 33
  • 56
  • Ah. This seems to be the fundamental difference -- "all leaves, or all nodes". it's interesting that `Bind` gives rise to such a restriction. This makes sense to me, thank you! :) – Justin L. Jun 09 '19 at 21:55