如何将免费monad转换为仿函数?

时间:2011-06-02 23:32:31

标签: haskell monads

Haskell wiki上的Free structure页面定义了一个将仿函数实例转换为免费monad的函数:

inj :: Functor f => f a -> Free f a
inj fa = Roll $ fmap Return fa

然后,说inj [1,2,3],类型为(Num t) => Free [] t。如何定义函数以将inj [1,2,3]之类的内容返回[1,2,3]

3 个答案:

答案 0 :(得分:15)

要注意的第一件事是inj的变体使Free成为几乎是单变换器的东西。

我会在我的Control.Monad.Free hackage包中使用free,以避免在此重复所有内容。这意味着相对于维基上的版本,Roll变为FreeReturn在下面的代码中被命名为Pure

import Control.Monad
import Control.Monad.Free -- from 'free'

instance MonadTrans Free where
    lift = Free . liftM Pure

然而,对于任意Functor,你不能朝另一个方向走。但是,如果您在Monad上有m的实例,则可以通过将Free m展平为基础monad m的单个图层来撤消提升!

retract :: Monad f => Free f a -> f a  
retract (Pure a) = return a
retract (Free as) = as >>= retract

选择名称是因为这是lift retract . lift = id 。所谓因为

retract (lift as) =                        -- by definition of lift
retract (Free (liftM Pure as)) =           -- by definition of retract
liftM Pure as >>= retract =                -- by definition of liftM
as >>= \a -> return (Pure a) >>= retract = -- first monad law
as >>= \a -> retract (Pure a)              -- by definition of retract
as >>= \a -> return a =                    -- eta reduction
as >>= return                              -- second monad law
as

所示
retract

所以函数lift撤消了fmap的工作。

由于liftM = inj,这也适用于lift . retract

请注意,id 不是 lift . retract . lift . retract = lift . retract。根本没有足够的空间将所有内容置于干预类型中 - 使用monad将所有内容都打平 - 但lift . retract . lift . retract = lift . id . retract = lift . retract因为lift . retract而保持,因此retract是幂等的。< / p>

这个'升力'的问题在于'升力'不是单子同态,而是仅仅是单子同态“向上收缩”,因此这推动了保留monad变换器定律的负担。提升计算,因此将inj作为单独的函数名称保留是有意义的。

我实际上是要立即将{{1}}添加到免费软件包中。我最近需要它来写一篇我正在写的文章。

答案 1 :(得分:11)

正如@sclv所说,在一般情况下,没有办法直接从仿函数的免费monad转换回仿函数。为什么不呢?

如果你回想起你所链接的“免费结构”页面,它首先会讨论免费的 monoids ,然后再扩展相同的概念来讨论 monads 。一个类型的自由幺半群是一个列表;在这种情况下,等效的“转换回”功能是将类型[a]的自由幺半群转换为单个元素,类型为a。这显然不可行于两种方式:如果列表为空,则不能返回任何内容;如果列表中有多个元素,则必须丢弃除一个元素以外的所有元素。

免费monad的构造是类似的,并提出了类似的问题。免费monad由仿函数合成定义,除了类型构造函数之外,它就像常规函数组合一样。我们不能直接在Haskell中编写functor组合,但就像f . g意味着\x -> f (g x)一样,我们可以嵌套类型构造函数的应用程序。例如,使用自身撰写Maybe会提供类似Maybe (Maybe a)的类型。

因此,换句话说,在一个普通的仿函数描述某种参数化结构的地方,该仿函数的自由monad描述了该结构嵌套在任意深度内。

因此,如果我们查看Free [] Int,则可以是单个IntInt列表,Ints列表,依此类推。

所以,就像我们只能将一个免费的monoid(列表)直接转换为单个项目(如果列表只有一个项目),我们只能将一个免费的monad直接转换为底层的functor 如果嵌套恰好是一层


如果你对从一个免费monad中取出东西的一般方法感兴趣,你需要更进一步 - 某种类似递归折叠的操作来折叠结构。

在列表的自由monad的特定情况下,有一个明显的方法 - 通过剥离RollReturn构造函数并连接列表来递归地展平结构。考虑为什么这种方法在这种情况下有效,以及它与列表结构的关系也可能具有启发性。

答案 2 :(得分:2)

我不明白你为什么要求这个功能,并且通常没有Free f a -> f a类型的单一功能。但是,inj的反转 - 也就是说,如果你知道结构是外卷,那么有一个的函数有一层回归。如果有更深的Roll s,那么这将失败并出现模式匹配错误,因此开始时这是一种愚蠢的事情。但是,你走了:

unInj :: Functor f => Free f a -> f a
unInj (Roll x) = fmap (\(Return y) -> y) x