haskell 理解>>=中的“单子m”

xfb7svmp  于 2022-11-14  发布在  其他
关注(0)|答案(6)|浏览(187)

看着 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类型是[],就我所知。

xj3cbfub

xj3cbfub1#

你说
我本以为\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

有时,[]描述的是一种“非决定论”效应。有时,[]描述的是一种类似容器的数据结构。事实上,很难区分这两种目的之间的区别,这是一些人非常自豪的一个特性。我不准备同意他们的观点,但我看到了他们在做什么。

imzjd6km

imzjd6km2#

看一下>>=的签名,\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

这就是Control.Monad模块中的join函数,对于单子,它与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函数的特殊化,它将嵌套的单子扁平化。
需要注意的是,单子可以用>>=fmapjoin来定义。
虽然 haskell 用return>>=函数来定义单子,但也可以用return和另外两个运算joinfmap来定义单子。这个公式更符合范畴论中单子的原始定义。fmap运算,类型为Monad m => (a -> b) -> m a -> m bjoin运算(类型为Monad m => m (m a) -> m a)将两层一元信息“展平”为一层。
这两个公式的关系如下:

fmap f m = m >>= (return . f)
join n   = n >>= id

m >>= g  ≡ join (fmap g m)

在这里,m具有Monad m => m a类型,n具有Monad m => m (m a)类型,f具有a -> b类型,g具有Monad m => a -> m b类型,其中ab是基础类型。
fmap函数是为类型和函数范畴中的任何函子定义的,而不仅仅是为单子定义的。它被期望满足函子定律:

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

return函数通过将值“提升”到函子中的能力来表征同一范畴中的指向函子。它应满足以下定律:

return . f ≡ fmap f . return

另外,join函数表征了单子:

join . fmap join     ≡ join . join
join . fmap return   ≡ join . return = id
join . fmap (fmap f) ≡ fmap f . join

希望能有所帮助。

vcirk6k6

vcirk6k63#

正如一些人所评论的,你在这里发现了一个关于单子的非常可爱的属性。作为参考,让我们看看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

我提到过这很可爱,因为通过扩展,这对 * 任何 * 嵌套单子都有效。

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

如果你看一下get在这里应用的函数,你会发现它是恒等函数(例如,idsame:: forall a. a -> a)。
它包含在Haskell的标准库中,名为join。你可以在hackage上查看源代码。你会看到它被实现为bind id\mma -> mma >>= id(=<<) id

bxfogqkk

bxfogqkk4#

正如你所说,m[],那么a[Integer](为了简单起见,忽略数字是多态的事实),bInteger,所以a -> m b变成了[Integer] -> [Integer]

cmssoen2

cmssoen25#

第一:我们应该使用same的标准版本,它被称为id
现在,让我们重命名一些类型变量

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

这意味着:id的签名是两个类型之间的函数Map,附加的约束是两个类型必须相等。仅此而已-我们不需要任何特殊的属性,比如“平坦”。
我为什么要这样写呢?如果我们也重命名绑定签名中的一些变量...

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

...那么我们如何插入id就很明显了,因为类型变量已经被相应地命名了。id的类型等式约束a''~a被简单地取为复合的签名,即:

(>>=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

所以,它把一个嵌套的单子扁平化为同一单子的一个应用,非常简单,事实上,这是一个“更基本”的操作:数学家们没有定义绑定算子,而是定义了两个态射η :: a -> m a(我们知道,它是return)和μ :: m (m a) -> m a--是的,这就是你刚刚发现的,在 haskell ,它被称为join

b4lqfgs4

b4lqfgs46#

这里的单子是[a],这个例子非常复杂,下面的例子会更清楚:

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

正如

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

>>=concatMap,并且当与id一起使用时是concat

相关问题