理解>> =中的“Monad m”

时间:2015-03-01 02:03:33

标签: haskell

看看Haskell的绑定:

Prelude> :t (>>=)
(>>=) :: Monad m => m a -> (a -> m b) -> m b

我对以下示例感到困惑:

Prelude> let same x = x

Prelude> [[1]] >>= \x -> same x
[1]

查看>>=的签名,\x -> same x如何键入a -> m b

我希望\x -> same x生成[b]类型,因为此处Monad m类型为[],据我所知。

6 个答案:

答案 0 :(得分:9)

你说

  

我希望\x -> same x生成[b]类型,因为此处的Monad m类型为[],据我了解。

因为它确实如此。

我们有

[[1]] >>= \ x -> same x
=
[[1]]       >>=    \ x -> x
[[Int]]          [Int] -> [Int]        :: [Int]
[] [Int]         [Int] -> [] Int       :: [] Int
m  a             a        m  b            m  b

有时候[]正在描述一种" nondeterminism"影响。其他时候,[]描述了类似容器的数据结构。事实上很难区分出这两个目的之间的区别是一些人非常自豪的特征。我还没准备好同意他们,但我知道他们正在做什么。

答案 1 :(得分:8)

  

查看>>=的签名,\x -> same x如何键入a -> m b

实际上非常简单。查看类型签名:

same       :: x -> x

(>>=)      :: Monad m => m a -> (a -> m b) -> m b

(>>= same) :: Monad m => m a -> (a -> m b) -> m b
                                |________|
                                    |
                                 x -> x

因此:

x := a

-- and

x := m b

-- and by transitivity

a := x := m b

-- or

a := m b

因此:

(>>= same) :: Monad m => m (m b) -> m b

这只是join模块中的Control.Monad函数,对于列表monad,它与concat函数相同。因此:

[[1]] >>= \x -> same x

-- is the same as the following via eta reduction

[[1]] >>= same

-- is the same as

(>>= same) [[1]]

-- is the same as

join [[1]]

-- is the same as

concat [[1]]

-- evaluates to

[1]
  

我希望\x -> same x生成[b]类型,因为此处Monad m类型为[],据我所知。

确实如此。如上所述,具有\x -> same x类型的x -> x函数专用于类型[b] -> [b]。因此,(>>= same)属于[[b]] -> [b]类型,与concat函数相同。它使列表列表变得平坦。

concat函数是join函数的特化,它使嵌套的monad变平。


应该注意,monad可以用>>=fmapjoin来定义。致quote Wikipedia

  

虽然Haskell根据return>>=函数定义了monad,但也可以根据return和其他两个操作定义monad,joinfmap。该公式更符合类别理论中monad的原始定义。类型为fmap的{​​{1}}操作在两种类型之间采用函数,并生成一个函数,该函数对monad中的值执行“相同的操作”。 Monad m => (a -> b) -> m a -> m b操作,类型为join,将两层monadic信息“展平”为一个。

     

这两种配方如下相关:

Monad m => m (m a) -> m a
     

此处,fmap f m = m >>= (return . f) join n = n >>= id m >>= g ≡ join (fmap g m) 的类型为mMonad m => m a的类型为nMonad m => m (m a)的类型为f,{{ 1}}的类型为a -> b,其中gMonad m => a -> m b是基础类型。

     

a函数是为类型和函数类别中的任何仿函数定义的,而不仅仅是monad。它有望满足仿函数法则:

b
     

fmap函数通过考虑将值“提升”到仿函数中的能力来表征同一类别中的尖头仿函数。它应该符合以下法律:

fmap id      ≡ id
fmap (f . g) ≡ (fmap f) . (fmap g)
     

此外,return函数表征monad:

return . f ≡ fmap f . return

希望有所帮助。

答案 2 :(得分:3)

正如一些人评论的那样,你在这里发现了一个非常可爱的关于单子的属性。作为参考,我们来看看bind的签名:

:: Monad m => m a -> (a -> m b) -> m b

在您的情况下,类型为a === m b,因为您有[[a]]m (m a)。因此,如果您重写上述绑定操作的签名,则会得到:

:: Monad m => m (m b) -> ((m b) -> m b) -> m b

我提到这很可爱,因为通过扩展,这适用于任何嵌套monad。 e.g。

:: [[b]] -> ([b] -> [b]) -> [b]
:: Maybe (Maybe b) -> (Maybe b -> Maybe b) -> Maybe b
:: Reader (Reader b) -> (Reader b -> Reader b) -> Reader b

如果您查看此处应用的函数,您会看到它是身份函数(例如idsame:: forall a. a -> a)。

这包含在Haskell的标准库中,join。您可以look at the source here on hackage.您会看到它已实施为bind id\mma -> mma >>= id(=<<) id

答案 3 :(得分:1)

正如您所说m[]。然后a[Integer](为了简单起见,忽略了数字是多态的这一事实)bInteger。因此a -> m b变为[Integer] -> [Integer]

答案 4 :(得分:1)

首先:我们应该使用same的标准版本,它被称为id

现在,让我们重命名一些类型变量

id :: (a'' ~ a) => a -> a''

这意味着:id的签名是两种类型之间的函数映射,具有两种类型相等的额外约束。这就是全部 - 我们不需要任何特定属性,例如“平坦”。

为什么我会用这种方式写它?好吧,如果我们还重命名绑定签名中的一些变量......

(>>=) :: (Monad m, a'~m a, a''~m b) => a' -> (a -> a'') -> a''

...然后很明显我们如何插入id,因为类型变量已经相应地命名。来自a''~a的类型等式约束id仅仅被用于复合词的签名,即

(>>=id) :: (Monad m, a'~m a, a''~m b, a''~a) => a' -> a''

或者,简化,

(>>=id) :: (Monad m, a'~m a, m b~a) => a' -> m b
(>>=id) :: (Monad m, a'~m (m b))    => a' -> m b
(>>=id) :: (Monad m)                => m (m b) -> m b

所以它的作用是,它将嵌套的monad展平为同一个monad的单个应用程序。非常简单,事实上这是一个“更基本”的操作:数学家don't define the bind operator, they instead define两个态射η :: a -> m a(我们知道,它是return)和{ {1}} - 是的,这是你刚刚发现的那个。在Haskell中,它被称为join

答案 5 :(得分:0)

这里的monad是[a],这个例子毫无意义。这将更清楚:

Prelude> [[1]] >>= id
[1]

就像

一样
Prelude> [[1]] >>= const [2]
[2]

即。与>>=一起使用时,concatMapconcat且为id