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]
?
答案 0 :(得分:15)
要注意的第一件事是inj
的变体使Free
成为几乎是单变换器的东西。
我会在我的Control.Monad.Free hackage包中使用free,以避免在此重复所有内容。这意味着相对于维基上的版本,Roll
变为Free
而Return
在下面的代码中被命名为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
,则可以是单个Int
,Int
列表,Ints
列表,依此类推。
所以,就像我们只能将一个免费的monoid(列表)直接转换为单个项目(如果列表只有一个项目),我们只能将一个免费的monad直接转换为底层的functor 如果嵌套恰好是一层。
如果你对从一个免费monad中取出东西的一般方法感兴趣,你需要更进一步 - 某种类似递归折叠的操作来折叠结构。
在列表的自由monad的特定情况下,有一个明显的方法 - 通过剥离Roll
和Return
构造函数并连接列表来递归地展平结构。考虑为什么这种方法在这种情况下有效,以及它与列表结构的关系也可能具有启发性。
答案 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