以与Free Monad兼容的方式定义Free Bind

时间:2019-06-04 04:08:11

标签: haskell category-theory free-monad

所以我们有免费的monad :(编码可能会有所不同,但是都一样)

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)

使得runFree形成monad同态,这是自由monad的定义属性。

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

我们还可以与免费的Bind from semigroupoids(我相信是这样)进行类似的构造,后者是仅绑定>>-的函子:

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

我们得到了适当的绑定同态:

使得runFree1形成绑定同态,这是定义属性:

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

现在,这两种类型都很棒。我们可以将Free1转换为Free,这很有意义:

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

,但是向后转换比较棘手。要从FreeFree1,我们必须处理两种情况:

  1. Free是纯净的,因此不能用Free1表示。
  2. Free不纯,因此可以用Free1表示。

这两种情况可以静态处理是有道理的,因为我们可以在PureImpure上进行匹配。

因此,合理的类型签名应该是:

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

但是我在写这篇文章时遇到了问题。

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

主要问题在于,我们需要确定是否使用DoneMore构造函数,而无需“运行” f。我们需要一个:

f (Free f a) -> Free1 f a

听起来像函子可能麻烦,如果您无法“离开”,例如IO

所以,这听起来是不可能的,除非我缺少任何东西。

我尝试了另一种编码:

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

让我们定义fromFree,它是从NonEmpty构造(data NonEmpty a = a :| [a])借用的。而且我在定义“免费Apply”时可以使用这种方法,这很好。这确实使我们可以编写toFreefromFreeliftFree1Bind实例。但是,我似乎无法写runFree1

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

只要我做任何事情,我似乎都需要return :: a -> g a,但是我们对所有Bind g都没有这个要求(我找到了可能进行类型检查的版本,但是它会执行两次效果,并且所以不是适当的绑定同态)

因此,尽管此方法为我们提供了fromFree,但我似乎找不到写runFree1的方法,这正是赋予它“免费Bind”功能的原因

在这两种方法中,我们要么:

  1. 使用Bind拥有实际的免费runFree1,但与Free不兼容,因为您无法将Free匹配成{{1 }}或纯值。
  2. 具有与Free1兼容的类型(可能是“非空Free”),但实际上不是自由的Free(没有Bind)并且失败整个目的。

由此我可以得出以下两个结论之一:

  1. 有一些方法可以制作与runFree1兼容的免费Free1绑定,但是我还无法弄清楚
  2. 从根本上说,我们不能有与Free兼容的免费Bind。这似乎与我的直觉相矛盾(我们总是可以立即确定Free是纯净的还是不纯净的,因此我们也应该能够立即区分纯净(零影响)还是Free),但是也许在那里是我更想念的更深层原因吗?

是哪种情况?如果是#1,那是方法,如果是#2,更深层的原因是什么? :)

提前谢谢!


编辑为了消除我是否正在使用“真正的免费绑定”的不确定性,我开始研究一种真正意义上的免费绑定:

Free1

我似乎也无法为此写newtype Free1 f a = Free1 { runFree1 :: forall g. Bind g => (forall x. f x -> g x) -> g a } 。最后,我似乎需要一个fromFree

如果我不能为此写g (Either a (Free1 a)) -> g a,那么就可以说我不能为自由绑定的任何实现写fromFree,因为所有实现都与此同构。

有没有办法为此写fromFree?还是全部不可能:'((对于fromFree / AltPlus / Apply来说都很好。

1 个答案:

答案 0 :(得分:4)

虽然Free f a是具有“ f节点”和“ a”叶的树的类型,但“自由绑定结构” Free1 f a却是这种类型的树。有其他限制的树:f节点的子节点要么全部是叶子,要么全部是f节点。因此,如果我们考虑二叉树:

data Bin x = Bin x x

然后Free Bin a包含以下树形,但Free1 Bin a不包含:

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

因为根节点有一个叶子和一个Bin节点作为子节点,而Free1 Bin a应该有两个叶子或两个Bin节点。这样的模式可能会在Free树的深处发生,因此仅凭Free f a -> Maybe (Free1 f a)约束就不可能进行部分转换Functor f。一个Traversable f约束(假设遍历是有限的)使转换成为可能,但是对于大树当然当然不可行,因为在产生任何输出之前必须完全遍历它们。

请注意,由于Free1的上述特征,因此根据Free的其他定义实际上并不等效:

data Free1 f a = Free1 (f (Free f a))  -- Not a free Bind