使用monads,可以根据绑定来定义连接吗?

Jac*_*ack 6 monads haskell

在Haskell中,monad是根据函数return和bind定义的,其中return有类型a -> m a,bind有类型m a -> (a -> m b) -> m b.之前已经指出monad也可以用return和join来定义,其中join是一个带有类型的函数m (m a) -> m a.绑定可以用连接来定义,但是反过来可能吗?可以在绑定方面加入定义吗?

没有加入,我不知道如果我以某种方式获得"两次包裹"的monadic值,我会做什么,m (m a)- 没有任何仿函数或monad操作"删除任何层",可以这么说.如果这是不可能的,为什么Haskell和许多其他monad实现在绑定方面定义它们?它似乎没有基于连接的定义有用.

chi*_*chi 10

有可能的:

join :: Monad m => m (m a) -> m a
join m = (m >>= id)
Run Code Online (Sandbox Code Playgroud)

请注意棘手的实例化>>=:

(>>=) :: m b -> (b -> m c) -> m c
-- choosing b ~ m a , c ~ a
(>>=) :: m (m a) -> (m a -> m a) -> m a
Run Code Online (Sandbox Code Playgroud)

所以我们可以正确选择id第二个参数.


小智 7

虽然这个问题已经得到了充分的回答,但我认为对于任何 Haskell 新手来说,以下几点值得注意。

在 Haskell 中学习 monad 时,您首先看到的一件事是列表的定义:

instance Monad [] where
    return x  =  [x]
    xs >>= f  =  concatMap f xs
Run Code Online (Sandbox Code Playgroud)

在这里,我们看到绑定列表的功能等同于concatMap,只是参数翻转了。这在检查类型时很有意义:

concatMap ::            (a ->  [b]) ->  [a] ->  [b]
bind      :: Monad m => (a -> m b ) -> m a  -> m b        -- (>>=) flips the arguments
Run Code Online (Sandbox Code Playgroud)

joinfor 列表的定义等价于
concat :: [[a]] -> [a].

函数的名称可能使它有点明显,但concat可以concatMap通过保持内部列表原样来恢复,以便取消“映射”部分concatMap

concatMap       id xs      
  = concat (map id xs)
  = concat (    id xs)
  = concat         xs      -- or simply 'concat = concatMap id'
Run Code Online (Sandbox Code Playgroud)

相同的属性适用于一般的 monad:

join x  =  x >>= id        -- or 'join = bind id'
Run Code Online (Sandbox Code Playgroud)

这真的来自这样一个事实

bind f m  =  join (fmap f m)
Run Code Online (Sandbox Code Playgroud)

以便

bind id m  =  join (fmap id m)           -- bind id  =  join . fmap id
           =  join (     id m)           --          =  join .      id
           =  join          m            --          =  join
Run Code Online (Sandbox Code Playgroud)

因为所有的 monad 首先都是 Functors,而且是 Functor 定律fmap id === id


Lee*_*Lee 6

是的,这很简单:

join m = m >>= id
Run Code Online (Sandbox Code Playgroud)


Ben*_*Ben 5

Bind ( >>=) 实际上确实“删除了一个层”:

(>>=) :: Monad m => m a -> (a -> m b) -> m b
Run Code Online (Sandbox Code Playgroud)

直观地说,它“a从“中获取一些s m a,然后将其提供给a -> m b函数,然后m b从结果中生成一个。

人们通常说它需要函数参数将其输出再次包装在 中m,但实际上并非如此。它要求函数的输出包裹在 中的东西m,但包裹来自哪里并不重要。

在实施的情况下,join我们从什么开始的“双包”: m (m a)。我们可以将其插入到 bind 的签名中,并立即找出在绑定“双重包装”值时我们可以使用的函数类型:

m (m a) -> (m a -> m b) -> m b
Run Code Online (Sandbox Code Playgroud)

现在与 bind 一起使用的函数将接收一个已经包含在m. 所以我们不必“重新包装”任何东西;如果我们不加修改地返回它,它就已经是输出的正确类型。这实际上是“去除了一层包装”——这适用于除最后一层之外的任何层。

所以这告诉我们我们只需要绑定id

join = (>>= id)
Run Code Online (Sandbox Code Playgroud)