Haskell 如何将自由单子转换为函子?
Haskell wiki上的页面定义了一个函数,用于将functor实例转换为自由单子:Haskell 如何将自由单子转换为函子?,haskell,monads,Haskell,Monads,Haskell wiki上的页面定义了一个函数,用于将functor实例转换为自由单子: 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]?我不明白您为什么要使用这个函数,而且通常没有一个类型为Free f a->f a的函数。然而,inj与之相反,
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]
?我不明白您为什么要使用这个函数,而且通常没有一个类型为Free f a->f a
的函数。然而,inj与之相反,也就是说,如果你知道结构是一个带有一层返回层的外辊,那么就有一个该类型的函数。如果有任何更深的Roll
s,那么这将失败,并出现模式匹配错误,因此从一开始就有点愚蠢。然而,你要做的是:
unInj :: Functor f => Free f a -> f a
unInj (Roll x) = fmap (\(Return y) -> y) x
正如@sclv所说,在一般情况下,无法直接从函子的自由单子转换回函子。为什么不呢 如果您还记得链接到的“自由结构”页面,它首先讨论自由幺半群,然后扩展相同的概念讨论单子。类型的自由幺半群是一个列表;在这种情况下,等效的“转换回”函数将把类型为
[a]
的自由幺半群转换为类型为a
的单个元素。这在两种不同的方式下显然是行不通的:如果列表为空,则不能返回任何内容;如果列表中有多个元素,则必须放弃除一个之外的所有元素
一个自由单子的构造是类似的,并提出了一个类似的问题。自由单子是由函子组合定义的,它与常规函数组合类似,只是在类型构造函数上不同。我们不能直接在Haskell中编写函子合成,但就像f一样。g
表示\x->f(gx)
,我们可以嵌套类型构造函数的应用程序。例如,将Maybe
与自身组合会得到类似Maybe(maybea)
的类型
换句话说,当普通函子描述某种参数化结构时,该函子的自由单子描述嵌套在其内部任意深度的结构
因此,如果我们看一下Free[]Int
,它可能是一个Int
,一个Int
列表,一个Int
列表,等等
因此,就像我们只能将一个自由单体(列表)直接转换为一个项目,如果列表正好有一个项目长,我们只能将一个自由单体直接转换为基础函子,如果嵌套正好有一层深
如果你对从一个自由的monad中提取东西的一般方法感兴趣,你需要更进一步——某种类似于递归折叠的操作来折叠结构
在列表的免费monad的特定情况下,有一种明显的方法——通过剥离
Roll
和Return
构造函数并在运行时连接列表,递归地扁平化结构。思考一下为什么这种方法在这种情况下有效,以及它与列表结构的关系,也可能会有所启发。首先要注意的是,inj
的一个变化使得自由
几乎变成了一个单子转换器
我将使用我的hackage软件包,以避免重复这里的所有内容。这意味着Roll
变为Free
,Return
在下面的代码中相对于wiki上的版本被命名为Pure
import Control.Monad
import Control.Monad.Free -- from 'free'
instance MonadTrans Free where
lift = Free . liftM Pure
但是,对于任意的函子
,您不能向另一个方向移动。但是,如果在m
上有一个Monad
实例,则可以通过将Free m
展平到底层Monadm
的单层来撤消提升
retract :: Monad f => Free f a -> f a
retract (Pure a) = return a
retract (Free as) = as >>= retract
之所以选择该名称,是因为这是提升的一个部分。所谓因为
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。收回
不是id
。根本没有足够的空间将所有内容都放在中间类型中——使用monad将所有内容都压扁——但是lift。收回举起收回=提升。缩回
保持,因为提升。收回举起收回=提升。身份证件收回=提升。缩回,因此提升。retract
是幂等的
这个“lift”的问题是,“lift”不是一个单子同态,而是一个单子同态“up to retract”,因此这将维护单子变换定律的负担推到了提升计算的用户身上,因此将inj保留为单独的函数名是有意义的
实际上,我现在就要把
retract
添加到免费软件包中。我最近在写一篇文章时需要它。非常感谢。我曾经错误地认为inj
函数比现在更聪明;所以真的没有办法“进去”或“出去”。另一方面,您确实暗示了一个可能的解决方案,这让我首先想到了免费
类型的(>=)
定义;然后我想到了奥列格·基塞柳夫和沃特·斯维斯特拉。@user643722:如果你喜欢这类事情,在哈斯凯尔,并在不同的地方写过关于他们的文章。例如,您可能感兴趣。也与当前主题相关——函子(,)Int
的自由单子是什么样子?@camccann:是的,我从Edward的输出中得到了我能得到的。谢谢你的提问,但我现在似乎有点不知所措;我所看到的只是一碗Roll
s和Return
s。尽管如此,我还是很高兴我以前没有注意到的非常明显的事情甚至可以从自由幺半群中看到:给定一个“输入”集,“输出”自由幺半群的二进制运算,没有