具有Join()而不是Bind()的单子


69

Monad通常以return和交替解释bind。但是,我认为您也可以bind根据join(和fmap?)实现

在缺乏一流功能的编程语言中bind,使用起来非常尴尬。join另一方面,看起来很简单。

但是,我不确定我是否了解join工作原理。显然,它具有[Haskell]类型

加入:: Monad m => m(mx)-> mx

对于清单monad来说,这显然是微不足道的concat。但是对于一般的monad而言,此方法实际上在做什么?我看到了它对类型签名的作用,但是我试图弄清楚如何用Java或类似语言编写类似的内容。

(实际上,这很容易:我不会。因为泛型已损坏。;-)但从原理上讲,问题仍然存在...)


哎呀。似乎以前有人问过这个问题:

Monad加入功能

可能有人勾画出使用普通的单子一些实现returnfmapjoin?(也就是说,一点也不提>>=。)我认为这也许可以帮助它沉入我的愚蠢大脑中……


3
该代码段有帮助吗?join m = m >>= id
Daniel Wagner 2012年

1
@DanielWagner我知道如何定义join。至少,我知道产生正确类型签名的咒语。但是,我正在努力地想尽一切办法真正做到一点……
MathematicalOrchid

9
@MathematicalOrchid:实际上没有join的一般定义,就像没有bind的一般定义一样。他们两个都定义了monad的功能!定义它们后,您将选择一个特定的monad。
porges 2012年

2
对于您而言,不管用什么价钱,这里都有一些Java中的join / cojoin实现:*读者monad * list monad *玫瑰树comonad * list-zipper comonad PS:我很讨厌格式化。
托尼·莫里斯

4
@MathematicalOrchid谢谢。所以我觉得你的意思是concat是一个容易被发现join候选人(给出一些基本知识的Haskell),而不是(即的One&Only)的可能性。我之所以提出这个问题,是出于担心读者可能会从字面上看得出您的评论并认为它是真实的,从而错过了一些启发性的研究思路。例如,是否concat满足所需的单子法则?还有其他候选人吗?如果分别为“是”和“否”,那么如何从法律中得出 concat结论?我用这样的问题使我远离“明显的”,即我的盲点。
康纳,

Answers:


102

在不深入探究隐喻的深度的情况下,我可能建议将典型的monad读m作“产生价值的策略”,因此这种类型m value是“产生价值的策略”的第一类。计算或外部交互的不同概念需要不同类型的策略,但是一般概念需要一些规则的结构才有意义:

  • 如果您已经有了一个值,那么您就有一种策略来产生一个值(return :: v -> m v),除了产生您所拥有的值外别无其他;
  • 如果您有一个将一种价值转换为另一种价值的函数,则可以fmap :: (v -> u) -> m v -> m u通过等待策略交付其价值然后将其转化来将其提升为策略();
  • 如果您有制定生产价值的策略的策略,则可以构造生产价值的策略(join :: m (m v) -> m v),该策略遵循外部策略,直到产生内部策略,然后一直遵循内部策略直至值。

让我们举个例子:叶子标记的二叉树...

data Tree v = Leaf v | Node (Tree v) (Tree v)

代表通过抛硬币生产东西的策略 如果战略是的Leaf v,那就是你的v; 如果策略为Node h t,则抛硬币,h如果硬币显示为“正面”,t则为策略,如果为“尾巴”则继续进行策略。

instance Monad Tree where
  return = Leaf

产生策略的策略是一棵树,上面有树标记的叶子:我们可以代替每个这样的叶子,将其嫁接到标记它的树上。

  join (Leaf tree) = tree
  join (Node h t)  = Node (join h) (join t)

...当然还有fmap重新标记叶子的标签

instance Functor Tree where
  fmap f (Leaf x)    = Leaf (f x)
  fmap f (Node h t)  = Node (fmap f h) (fmap f t)

这是产生策略的策略Int

树的树

投掷硬币:如果是“正面”,则投掷另一枚硬币以决定两种策略(分别生产“投掷硬币以产生0或产生1”或“产生2”);如果是“尾巴”,则产生第三只(“掷硬币以产生3枚或掷硬币以产生4枚或5枚”)。

显然join可以制定一个产生收益的战略Int

在此处输入图片说明

我们正在利用的事实是,“产生价值的战略”本身可以视为价值。在Haskell中,将策略作为值的嵌入是沉默的,但是在英语中,我使用引号来区分使用策略和仅谈论策略。该join运营商表示战略“不知何故产生然后按照战略”,或者“如果你告诉一个战略,你便可以使用它。”

(元。我不确定这种“策略”方法是否适合用于思考monads和值/计算区别的通用方法,或者它是否只是另一个拙劣的隐喻。我确实发现以叶标记的树状类型是有用的直觉的来源,这也许不足为奇,因为它们是免费的monad,其结构足以成为monad,但仅此而已。)

PS“绑定”的类型

(>>=) :: m v -> (v -> m w) -> m w

表示“如果您有一个产生a的策略v,并且为每个va后续策略产生a w,那么您就有一个产生a的策略w”。我们如何才能从中捕捉到这一点join

mv >>= v2mw = join (fmap v2mw mv)

我们可以通过标记v生产策略v2mw,而不是通过每个v值来w生产随之而来的生产策略,即准备好join


13
@Jamil如果您认为这很有帮助,则应该查看Dan Piponi撰写的文章Monads Are Trees with Grafting。它显示了如何以这种方式查看所有monad。
克里斯·泰勒

8
@Jamil(谢谢!)在monad列表中,“产生价值的策略”包括提供价值选择。concat将选择商品之间的选择变成选择商品,例如将满是商店的街道(先选择商店,然后选择商品)变成一个大型超市。
养猪工人

3
是的,它确实。我们v2mw :: v -> m wfmap :: (a -> b) -> m a -> m b因此fmap v2mw与typechecksa = vb = m w给予型m v -> m (m w),这也是为什么join事后给你m w
Pigworker 2014年

3
就自然语言的隐喻而言,“产生价值的策略”听起来不像“上下文中的价值”那么笨拙。
szablica'Aug

3
Chris Taylor的链接来自Dan Piponi的博客。现在链接已死。Dan Piponi提供了更新的链接
leewz

31
join = concat -- []
join f = \x -> f x x -- (e ->)
join f = \s -> let (f', s') = f s in f' s' -- State
join (Just (Just a)) = Just a; join _ = Nothing -- Maybe
join (Identity (Identity a)) = Identity a -- Identity
join (Right (Right a)) = Right a; join (Right (Left e)) = Left e; 
                                  join (Left e) = Left e -- Either
join ((a, m), m') = (a, m' `mappend` m) -- Writer
-- N.B. there is a non-newtype-wrapped Monad instance for tuples that
-- behaves like the Writer instance, but with the tuple order swapped
join f = \k -> f (\f' -> f' k) -- Cont

4
好吧,这就是我要的。;-)有点...虽然真的很密集。也许如果我真的很慢地看它还是……
MathematicalOrchid

19

调用产生价值所以它是一个很自然的事情用取回值fmap (f :: a -> m b) (x ::ma)(y ::m(m b))join(z :: m b)

然后将bind定义为bind ma f = join (fmap f ma),从而实现了多样化功能的Kleisly组合(:: a -> m b),这实际上是关于:

ma `bind` (f >=> g) = (ma `bind` f) `bind` g              -- bind = (>>=)
                    = (`bind` g) . (`bind` f) $ ma 
                    = join . fmap g . join . fmap f $ ma

因此,有flip bind = (=<<)我们有

    ((g <=< f) =<<)  =  (g =<<) . (f =<<)  =  join . (g <$>) . join . (f <$>)

克莱斯里组成


旁注:(return . f' =<<) = (f' <$>) = join . (return . f' <$>)(g' <$>) . (f' <$>) = (return . g' =<<) . (return . f' =<<) = (return . g') <=< (return . f')。(NB f'和g'在这里是“水平”,而不是“对角线”。)
Will Ness

16

好的,所以回答您自己的问题并不是一种很好的形式,但是我要记下我的想法,以防他人启发。(我对此表示怀疑...)

如果一个单子可以被看作是一个“容器”,那么这两个returnjoin具有很明显的语义。return生成一个1元素的容器,然后join将一个容器的容器变成一个容器。没什么难的。

因此,让我们关注更自然地被认为是“动作”的单子。在这种情况下,m x某种动作会x在您“执行”时产生type值。return x没什么特别的,然后产生xfmap f采取产生一个的动作x,并构造一个计算x然后应用于该动作f并返回结果的动作。到现在为止还挺好。

很明显,如果f它本身产生一个动作,那么最终的结果就是m (m x)。也就是说,一个动作将计算另一个动作。在某种程度上,这比起>>=采取行动的功能和“产生行动的功能”等更容易使您着迷。

因此,从逻辑上讲,似乎join将运行第一个动作,执行它产生的动作,然后运行该动作。(更确切地说,join,如果您想分开头发将返回执行我刚才描述的操作。)

这似乎是中心思想。要实现join,您需要运行一个动作,然后再给您另一个动作,然后运行该动作。(无论“运行”对这个特定的monad意味着什么。)

有了这种见识,我可以尝试编写一些join实现:

join Nothing = Nothing
join (Just mx) = mx

如果外部动作为Nothing,则返回Nothing,否则返回内部动作。再说一次,Maybe它更多的是一个容器而不是一个动作,所以让我们尝试其他的东西...

newtype Reader s x = Reader (s -> x)

join (Reader f) = Reader (\ s -> let Reader g = f s in g s)

真是无痛。AReader实际上只是一个具有全局状态并随后返回其结果的函数。因此,要取消堆叠,请将全局状态应用于外部操作,该操作会返回一个新的Reader。然后,您也将状态应用于此内部函数。

在某种程度上,它可能比通常的方法容易

Reader f >>= g = Reader (\ s -> let x = f s in g x)

现在,哪个是阅读器功能,哪个是计算下一个阅读器的功能...?

现在,让我们尝试一下好老的Statemonad。在这里,每个函数都将初始状态作为输入,但也会返回新状态及其输出。

data State s x = State (s -> (s, x))

join (State f) = State (\ s0 -> let (s1, State g) = f s0 in g s1)

不太难。基本上先运行,然后运行。

我现在要停止输入。随意指出我示例中的所有小故障和错别字...:-/


6
我只发现了一个错误-缺少paren join (Just mx)。另外,回答您自己的问题也不错。blog.stackoverflow.com/2011/07/...
本·米尔伍德

13

我已经找到了许多有关Monad的解释,这些解释说:“实际上,您不必了解类别理论的任何知识,只需将Monad视为墨西哥卷饼/宇航服/随便什么”。

确实,为我解开单子的神秘的文章只是说了什么是类别,以类别描述了单子(包括连接和绑定),并且没有理会任何虚假的隐喻:

我认为这篇文章可读性强,不需要太多数学知识。


4

问Haskell的类型签名有什么作用,就像问Java的接口有什么作用

从字面上看,它“不”。(不过,当然,通常会有某种目的与之相关,这主要是在您的脑海中,而不是在实现中。)

在这两种情况下,您都将使用将在以后的定义中使用的语言声明符号的合法顺序。

当然,在Java中,我想您可以说一个接口对应于一个类型签名,该类型签名将在虚拟机中逐字地实现。您可以通过这种方式获得一些多态性-您可以定义一个接受接口的名称,并且可以为该名称提供一个不同的定义,以接受不同的接口。在Haskell中也会发生类似的情况,您可以在其中提供一个名称的声明,该名称接受一种类型,然后为该名称提供另一种声明,以处理不同的类型。


2

这是莫纳德在一张照片中解释的。绿色类别中的2个功能是不可组合的,当映射到蓝色类别(严格来说,它们是一个类别)时,它们就可以组合了。Monad是关于将类型T -> Monad<U>的函数转换为的函数Monad<T> -> Monad<U>

莫纳德在一张照片中解释。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.