看看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
类型为[]
,据我所知。
答案 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可以用>>=
或fmap
和join
来定义。致quote Wikipedia:
虽然Haskell根据
return
和>>=
函数定义了monad,但也可以根据return
和其他两个操作定义monad,join
和fmap
。该公式更符合类别理论中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)
的类型为m
,Monad m => m a
的类型为n
,Monad m => m (m a)
的类型为f
,{{ 1}}的类型为a -> b
,其中g
和Monad 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
如果您查看此处应用的函数,您会看到它是身份函数(例如id
,same
,:: forall a. a -> a
)。
这包含在Haskell的标准库中,join
。您可以look at the source here on hackage.您会看到它已实施为bind id
,\mma -> mma >>= id
或(=<<) id
答案 3 :(得分:1)
正如您所说m
是[]
。然后a
为[Integer]
(为了简单起见,忽略了数字是多态的这一事实)b
是Integer
。因此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]
即。与>>=
一起使用时,concatMap
为concat
且为id
。