Monad通常用return和来解释bind.不过,我猜想你也可以实现bind在以下方面join(和fmap?)
在缺乏一流功能的编程语言中,使用起来bind非常难以捉摸.join另一方面,看起来很容易.
但是,我并不完全确定我理解它是如何join运作的.显然,它有[Haskell]类型
join :: Monad m => m (m x) -> m x
对于monad列表,这显然很简单concat.但是对于一般的monad来说,这种方法在操作上实际上做了什么?我看到它对类型签名的作用,但我试图弄清楚我是如何在Java或类似的东西中写出这样的东西.
(实际上,这很容易:我不会.因为仿制药已经坏了.;-)但原则问题仍然存在......)
哎呀.看起来之前有人问过:
可能有人勾画出使用普通的单子一些实现return,fmap和join?(即,根本没有提到>>=.)我想也许这可能有助于它沉入我愚蠢的大脑......
pig*_*ker 96
如果没有管道隐喻的深度,我可能会建议将典型的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)
Run Code Online (Sandbox Code Playgroud)
......代表通过掷硬币来制作东西的策略.如果策略是Leaf v,那就是你的v; 如果策略是Node h t,h如果硬币显示"头",t如果它是"尾巴" ,你就掷硬币并继续策略.
instance Monad Tree where
return = Leaf
Run Code Online (Sandbox Code Playgroud)
策略生成策略是一棵带有树标记叶子的树:代替每一片这样的叶子,我们可以只在树上标记它的树...
join (Leaf tree) = tree
join (Node h t) = Node (join h) (join t)
Run Code Online (Sandbox Code Playgroud)
...当然,我们fmap只有相关的叶子.
instance Functor Tree where
fmap f (Leaf x) = Leaf (f x)
fmap f (Node h t) = Node (fmap f h) (fmap f t)
Run Code Online (Sandbox Code Playgroud)
这是制定生产战略的策略Int.

掷硬币:如果它是"头",则抛出另一枚硬币来决定两种策略(分别产生"掷硬币生产0或生产1"或"生产2"); 如果它是"尾巴"产生第三个("投掷硬币生产3或掷硬币4或5").
显然join,制定战略的目标正是如此Int.

我们正在利用的是,"产生价值的战略"本身可以被视为一种价值.在Haskell中,策略作为值的嵌入是沉默的,但在英语中,我使用引号来区分使用策略而不仅仅是谈论它.该join运营商表示战略"不知何故产生然后按照战略",或者"如果你告诉一个战略,你便可以使用它."
(Meta.我不确定这种"策略"方法是否是一种适当的通用方式来考虑monad和值/计算的区别,或者它是否只是另一个糟糕的比喻.我确实发现叶子标记的树状类型是有用的直觉的来源,也许不是一个惊喜,因为它们是自由的单子,只有足够的结构才能成为单子,但不多了.)
PS"绑定"的类型
(>>=) :: m v -> (v -> m w) -> m w
Run Code Online (Sandbox Code Playgroud)
他说:"如果你有一个策略来制作一个v,并且每个va后续策略都要产生一个w,那么你就有了制作一个策略w".我们怎样才能捕捉到这一点join?
mv >>= v2mw = join (fmap v2mw mv)
Run Code Online (Sandbox Code Playgroud)
我们可以重新v制定我们的生产策略v2mw,从而生成代替每个v价值的w生产策略 - 随时准备join!
Dan*_*ner 28
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
Run Code Online (Sandbox Code Playgroud)
Mat*_*hid 15
好的,所以回答你自己的问题并不是一个很好的形式,但我会记下我的想法,以防它启发任何其他人.(我对此表示怀疑...)
如果一个单子可以被看作是一个"容器",那么这两个return和join具有很明显的语义.return生成一个1元素的容器,并将join容器的容器转换为单个容器.没什么难的.
因此,让我们专注于更自然地被认为是"行动"的单子.在这种情况下,m x某种行为会x在您"执行"它时产生类型值.return x没有什么特别的,然后收益x.fmap f采取一个动作,产生一个x,并构造一个计算x然后应用f它的动作,并返回结果.到现在为止还挺好.
很明显,如果f自己产生一个动作,那么你最终会得到什么m (m x).也就是说,计算另一个动作的动作.在某种程度上,这可能比>>=采取行动的功能和"产生动作的功能"等包围你的想法更简单.
所以,从逻辑上讲,它似乎join会运行第一个动作,采取它产生的动作,然后运行它.(或者更确切地说,join如果你想分裂头发,会返回一个我刚刚描述过的动作.)
这似乎是中心思想.要实现join,您希望运行一个操作,然后为您提供另一个操作,然后运行该操作.(无论"奔跑"是什么意味着这个特定的monad.)
鉴于这种见解,我可以尝试编写一些join实现:
join Nothing = Nothing
join (Just mx) = mx
Run Code Online (Sandbox Code Playgroud)
如果外部动作是Nothing,则返回Nothing,否则返回内部动作.再说一次,Maybe更多的是一个容器而不是一个动作,所以让我们尝试别的......
newtype Reader s x = Reader (s -> x)
join (Reader f) = Reader (\ s -> let Reader g = f s in g s)
Run Code Online (Sandbox Code Playgroud)
那......无痛苦.A Reader实际上只是一个采用全局状态的函数,然后才返回其结果.因此,对于取消堆栈,您将全局状态应用于外部操作,后者返回一个新操作Reader.然后,您也可以将状态应用于此内部函数.
在某种程度上,它可能比通常的方式更容易:
Reader f >>= g = Reader (\ s -> let x = f s in g x)
Run Code Online (Sandbox Code Playgroud)
现在,哪一个是读者功能,哪一个是计算下一个读者的功能......?
现在让我们尝试一下古老的Statemonad吧.这里每个函数都将初始状态作为输入,但也返回一个新状态及其输出.
data State s x = State (s -> (s, x))
join (State f) = State (\ s0 -> let (s1, State g) = f s0 in g s1)
Run Code Online (Sandbox Code Playgroud)
那不是太难.它基本上运行然后运行.
我现在要打算停止打字了.随意指出我的例子中的所有故障和拼写错误...: - /
小智 12
我发现很多关于monad的解释说"你不必知道关于类别理论的任何事情,真的,只要将monads想象为墨西哥卷饼/太空服/无论什么".
真的,那个为我揭开神秘面纱的文章只是说了什么类别,在类别方面描述了monad(包括加入和绑定),并没有打扰任何虚假的比喻:
我认为这篇文章非常易读,不需要太多的数学知识.
Wil*_*ess 12
呼叫产生价值所以它是一个很自然的事情用取回值.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
Run Code Online (Sandbox Code Playgroud)
所以flip bind = (=<<),我们有
((g <=< f) =<<) = (g =<<) . (f =<<) = join . (g <$>) . join . (f <$>)
Run Code Online (Sandbox Code Playgroud)
询问 Haskell 中的类型签名的作用就像询问 Java 中的接口的作用一样。
从字面意义上来说,它“不”。(当然,您通常会有某种与之相关的目的,这主要是在您的脑海中,而不是在实现中。)
在这两种情况下,您都在声明语言中的合法符号序列,这些符号将在后面的定义中使用。
当然,在 Java 中,我想你可以说接口对应于类型签名,该类型签名将在虚拟机中逐字实现。您可以通过这种方式获得一些多态性——您可以定义一个接受接口的名称,并且可以为接受不同接口的名称提供不同的定义。Haskell 中也会发生类似的情况,您可以为接受一种类型的名称提供一个声明,然后为该名称提供另一个处理不同类型的声明。
| 归档时间: |
|
| 查看次数: |
11708 次 |
| 最近记录: |