Haskell中的do块是否会隐式地强制类型转换?

6
我的问题与另一个问题的答案有关:https://dev59.com/hWbWa4cB1Zd3GeqPas0U#11766789 在他的答案中,ertes写下了以下类型签名。
select :: [a] -> [(a, [a])]

然而,当实际使用select时,ertes会在do块中写入以下内容。

(y, ys) <- select xs

请帮我解释一下元组 (y, ys) 如何匹配 select 函数的返回类型,即 [(a, [a])]。Haskell 是否在某个时候对类型进行了强制转换?(Haskell 是否曾经进行过类型强制转换?)<- 是从 select 返回的列表单子中提取类型为 (a, [a]) 的元组吗?
谢谢, Max
---编辑:---
@Lee 提醒新手在尝试理解类型之前要进行 desugar。展开语法糖后,问题函数看起来像这样:
select xs >>= \(y, ys) -> fmap (y:) (perms (n - 1) ys)

对于列表,xs >>= f = concat (map f xs)。因此,在这个上下文中更好地解读 (y, ys) 是将要映射到列表上的函数的签名。


3
select 返回一个列表。如果你写 a <- select xs,那么 a 就是 select xs 的每个元素。如果之后你写了 func a,那么这个的结果就是将 func 应用于 select xs 中的每个元素得到的结果。这就是列表单子的定义方式。a <- select xs; func aselect xs >>= func 是一样的。对于列表绑定操作的定义为: m >>= f = concat (map f m),所以你可以写成 map func (select xs)。在 Haskell 中没有类型强制转换。 - user2407038
@user2407038 这里有一点小技术细节;a 不是指代 '每个元素',而是表示列表中的一个(任意但具体的)元素。 - Justin L.
2个回答

13

do 表示法中,

do x1 <- action1
   action2

被翻译成 action1 >>= \x1 -> action2

这意味着如果action1是某个单子m的类型m a,那么x1的类型为a。它并不是在强制转换类型,而是从单子操作action1中“解包”值,并将其绑定到x1上。


4

(y, ys) 的类型为 (b, c)

select 的返回类型为 [(a, [a])]

<- 中,类型实际上是 dMonad m => m d。因此我们可以写出以下类型相等:

(b, c) ~ d
[(a, [a])] ~ Monad m => m d

解决问题很容易。首先将第一个方程中的d替换为第二个方程:
[(a, [a])] ~ Monad m => m (b, c)

现在我将使用一种[]类型构造函数的前缀形式来查看正在发生的事情(它并不是有效的Haskell,但你应该能够理解):
[] (a, [a]) ~ Monad m => m ( b, c)

So

m ~ []
(a, [a]) ~ (b, c)

此时编译器会检查是否存在 instance Monad [a]。其余部分很容易:

a ~ b
[a] ~ c

1
[] (a, [] a) 实际上是有效的 Haskell,甚至可以使用 [] ((,) a ([] a)) 如果你喜欢的话。 - Jon Purdy
哦,我不知道 [] 的类型构造函数也可以像值构造函数一样使用。 - nponeccop
是的,我也很惊讶地发现了这一点。这无疑使得解释列表单子变得更容易了。 - Jon Purdy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接