Mic*_*ann 9 monads haskell category-theory
我的印象是(>>=)(由Haskell使用)和join(由数学家首选)是“相等的”,因为一个人可以用另一个来写:
import Control.Monad (join)
join x = x >>= id
x >>= f = join (fmap f x)
Run Code Online (Sandbox Code Playgroud)
另外,每个monad都是函子,因为bind可以用来代替fmap:
fmap f x = x >>= (return . f)
Run Code Online (Sandbox Code Playgroud)
我有以下问题:
是否有一个(非递归)的定义fmap来讲join?(fmap f x = join $ fmap (return . f) x根据上面的方程式,但是是递归的。)
使用时bind(在monad的定义中),“每个monad都是函子”是结论,而使用时是假设join吗?
是bind不是更“强大” join?“更强大”意味着什么?
是否有一个[定义]
fmap在以下方面join?
不,没有。这可以通过尝试这样做来证明。假设我们有一个任意类型的构造T函数和函数:
returnT :: a -> T a
joinT :: T (T a) -> T a
Run Code Online (Sandbox Code Playgroud)
仅从这些数据,我们想定义:
fmapT :: (a -> b) -> T a -> T b
Run Code Online (Sandbox Code Playgroud)
所以让我们勾画它:
fmapT :: (a -> b) -> T a -> T b
fmapT f ta = tb
where
tb = undefined -- tb :: T b
Run Code Online (Sandbox Code Playgroud)
我们需要以T b某种方式获得一个类型的值。ta :: T a靠它自己是不行的,所以我们需要产生T b值的函数。仅有的两个候选者是joinT和returnT。joinT没有帮助:
fmapT :: (a -> b) -> T a -> T b
fmapT f ta = joinT ttb
where
ttb = undefined -- ttb :: T (T b)
Run Code Online (Sandbox Code Playgroud)
它只是把罐子踢倒了,因为T (T b)在这种情况下需要一个值是没有改进的。
我们可能会尝试returnT:
fmapT :: (a -> b) -> T a -> T b
fmapT f ta = returnT b
where
b = undefined -- b :: b
Run Code Online (Sandbox Code Playgroud)
现在我们需要一个b值。唯一可以给我们的是f:
fmapT :: (a -> b) -> T a -> T b
fmapT f ta = returnT (f a)
where
a = undefined -- a :: a
Run Code Online (Sandbox Code Playgroud)
现在我们被困住了:没有什么可以给我们一个a. 我们已经用尽了所有可用的可能性,所以fmapT不能用这样的术语来定义。
题外话:使用这样的函数来作弊是不够的:
extractT :: T a -> a
Run Code Online (Sandbox Code Playgroud)
使用extractT,我们可能会尝试a = extractT ta,导致:
fmapT :: (a -> b) -> T a -> T b
fmapT f ta = returnT (f (extractT ta))
Run Code Online (Sandbox Code Playgroud)
然而,仅仅fmapT拥有正确的类型是不够的:它还必须遵循函子定律。尤其fmapT id = id应该持有。有了这个定义,fmapT idis returnT . extractT,一般来说,不是id(大多数函子都是两者的实例Monad并Comonad作为例子)。
在使用
bind(在 monad 的定义中)时,“每个 monad 都是函子”是一个结论,但在使用时是一个假设join吗?
“每个 monad 都是函子”是一个假设,或者更准确地说,是 monad 定义的一部分。要选择一个任意的插图,这里是 Emily Riehl, Category Theory In Context,p。154:
定义 5.1.1。甲单子上的类C由
一个内函子T : C ? C,
一个单元自然转化?:1 ℃?Ť,并
一个乘法自然转化?:T 2?牛逼,
以便以下图表在 C C中交换:[monad 定律的图表]
因此,根据定义,单子包含一个内函子。对于T实例化的 Haskell 类型构造函数,Monad该内函子的对象映射是T它本身,而态射映射是它的fmap。这T将是一个Functor实例,因此将有一个fmap, is,在当代 Haskell 中,保证Applicative(并且,通过扩展,Functor)是 的超类Monad。
不过,这就是故事的全部吗?就 Haskell 而言。我们知道liftM存在,而且在不远的过去Functor不是 的超类Monad。这两个事实仅仅是哈斯克尔主义吗?不完全的。在经典论文Notions of computing and monads 中,Eugenio Moggi 揭示了以下定义(第 3 页):
定义 1.2 ([Man76])类别C上的Kleisli 三元组是三元组(T, ?, _*),其中T : Obj( C ) ? 对象( C ), ? 答:一个?TA的一个?Obj( C ), f* : TA ? TB为f : A ? TB和以下等式成立:
- ? A * = id T A
- ? 一个; f* = f 为 f : A ? 结核病
- F*; g* = (f; g*)* 对于 f : A ? TB 和 g : B ? 技术委员会
这里最重要的细节是,Ť被呈现为在类别仅仅是一个对象映射Ç,而不是如在endofunctor Ç。在Hask类别中工作,这相当于采用类型构造函数T而不预先假定它是一个Functor实例。在代码中,我们可以这样写:
class KleisliTriple t where
return :: a -> t a
(=<<) :: (a -> t b) -> t a -> t b
-- (return =<<) = id
-- (f =<<) . return = f
-- (g =<<) . (f =<<) = ((g =<<) . f =<<)
Run Code Online (Sandbox Code Playgroud)
将绑定放在一边,这是MonadHaskell 中的 pre-AMP 定义。不出所料,Moggi 的论文很快就表明“在 Kleisli 三元组和单子之间存在一一对应的关系”(第 5 页),并建立了T可以扩展到一个内函子(在 Haskell 中,这一步相当于定义态射映射liftM f m = return . f =<< m,然后显示它遵循函子定律)。
总而言之,如果你写的合法定义return并(>>=)没有预先假定fmap,你确实得到了合法的执行Functor结果。“Kleisli 三元组和 monad 之间存在一一对应”是 Kleisli 三元组定义的结果,而“monad 涉及一个内函子”是 monad 定义的一部分。很容易考虑将 Haskellers 在编写Monad实例时所做的描述为“设置 Klesili 三元组”而不是“设置单子”是否更准确,但我会避免陷入术语学究的困境 - - 无论如何,现在这Functor 是一个超类,Monad没有实际理由担心这一点。
是
bind不是更“强大”join?“更强大”是什么意思?
套路问题!
从表面上看,答案是肯定的,在某种程度上,与一起return,(>>=)使得它可以实现fmap(通过liftM,如上所述),而join不会。但是,我认为坚持这种区分是不值得的。为什么这样?因为单子定律。就像它没有意义谈论一个合法的 (>>=)没有预先假定return,它没有意义谈论合法join不pressuposingreturn 和 fmap。
人们可能会觉得我通过使用它们来捆绑Monad和Functor以这种方式过于重视法律。确实存在涉及两个类的法律案例,并且仅适用于实例化它们的类型。Foldable提供了一个很好的例子:我们可以发现以下规律的Traversable文档:
超类实例应满足以下条件:[...]
在这个
Foldable例子中,foldMap应该等价于用一个常数应用函子 (foldMapDefault)进行遍历。
这个特定的定律并不总是适用不是问题,因为我们不需要它来表征是什么Foldable(替代方案包括“aFoldable是一个容器,我们可以从中提取一些元素序列”,以及“aFoldable是一个容器可以转换为其元素类型的自由幺半群”)。然而,对于 monad 法则,它不是那样的:类的含义与所有三个 monad 法则密不可分。
一个monad的定义如下:
return :: a -> m abind :: m a -> (a -> m b) -> m b或者在以下方面:
return :: a -> m afmap :: (a -> b) -> m a -> m bjoin :: m (m a) -> m a对您的问题:
fmap来讲join,因为否则我们可能会删除fmap从上面的第二个列表。bind还是使用join和定义特定的monad fmap。如果您看到第二个定义,则更容易理解该语句,仅此而已。bind比join。如果您用“强大”来表示它具有定义monad的能力(总是与结合使用),则它与“强大” 完全一样,join并且fmap组合在一起return。有关直觉,请参见此答案 - bind允许您将策略/计划/计算(在上下文中)组合或链接在一起。例如,让我们使用Maybe上下文(或Maybemonad):
?: let plusOne x = Just (x + 1)
?: Just 3 >>= plusOne
Just 4
Run Code Online (Sandbox Code Playgroud)
fmap 还可以让您将上下文中的计算链接在一起,但是会以增加每一步的嵌套为代价。[1]
?: fmap plusOne (Just 3)
Just (Just 4)
Run Code Online (Sandbox Code Playgroud)
这就是为什么您需要join:将两个嵌套层次压缩成一个层次。记得:
join :: m (m a) -> m a
Run Code Online (Sandbox Code Playgroud)
仅执行挤压步骤不会使您走得太远。您还fmap需要一个monad –和return,Just在上面的示例中。
[1]:fmap并且(>>=)不要以相同的顺序接受他们的两个论点,但是不要让这混淆了您。
| 归档时间: |
|
| 查看次数: |
220 次 |
| 最近记录: |