Haskell 的 (<-) 在 Monad 的自然转换方面

Haskell's (<-) in Terms of the Natural Transformations of Monad

所以我正在使用 GHCi 中的 hasbolt 模块,我对一些脱糖感到好奇。我一直在通过如下创建管道连接到 Neo4j 数据库

ghci> pipe <- connect $ def {credentials}

而且效果很好。但是,我想知道 (<-) 运算符的类型是什么(GHCi 不会告诉我)。大多数脱糖解释都描述了

do x <- a
   return x

脱糖为

a >>= (\x -> return x)

但是 x <- a 行呢? 添加 return 对我没有帮助,因为我想要 pipe :: Pipe 而不是 pipe :: Control.Monad.IO.Class.MonadIO m => m Pipe,而是 (>>=) :: Monad m => m a -> (a -> m b) -> m b 所以尝试使用 bind 和 [=17 脱糖=]/pure 没有它就无法工作。

理想情况下,最好只创建一个 Comonad 实例以启用 extract :: Monad m => m a -> a 作为 pipe = extract $ connect $ def {creds} 的使用,但这让我很烦恼,我不明白 (<-).

另一个奇怪的是,将 (<-) 视为 haskell 函数,它的第一个参数是一个超出范围的变量,但这并不意味着

(<-) :: a -> m b -> b

因为不是任何东西都可以用作自由变量。例如,您无法将管道绑定到 Num 类型或 Bool。该变量必须是 "String" 左右的东西,除非它实际上不是 String;而且您绝对不能尝试实际绑定到 String。所以它似乎不是通常意义上的 haskell 函数(除非有一个 class 函数从自由变量命名空间获取值......不太可能)。那么 (<-) 到底是什么?可以用extract完全代替吗?这是 desugar/circumvent 的最佳方式吗?

I'm wondering what the type of the (<-) operator is ...

<- 没有类型,它是 do 符号语法的一部分,如您所知,它被转换为 >>=return 的序列在称为脱糖的过程中。

but what about just the line x <- a ...?

这是普通 haskell 代码中的语法错误,编译器会报错。行的原因:

ghci> pipe <- connect $ def {credentials}

在 ghci 中的作用是 repl 是一种 do 块;您可以将每个条目视为 main 函数中的一行(比那更复杂一些,但这是一个很好的近似值)。这就是为什么你需要(直到最近)在 ghci 中说 let foo = bar 来声明绑定。

Ideally it seems like it'd be best to just make a Comonad instance to enable using extract :: Monad m => m a -> a as pipe = extract $ connect $ def {creds} but it bugs me that I don't understand (<-).

Comonad 与 Monad 无关。事实上,大多数 Monad 没有任何有效的 Comonad 实例。考虑 [] Monad:

instance Monad [a] where
  return x = [x]
  xs >>= f = concat (map f xs)

如果我们尝试编写一个 Comonad 实例,我们无法定义 extract :: m a -> a

instance Comonad [a] where
  extract (x:_) = x
  extract [] = ???

这告诉我们关于 Monad 的一些有趣的事情,即我们不能写一个类型为 Monad m => m a -> a 的通用函数。换句话说,我们不能在没有额外知识的情况下 "extract" 来自 Monad 的值。

那么 do-notation 语法 do {x <- [1,2,3]; return [x,x]} 是如何工作的?

因为 <- 实际上只是语法糖,就像 [1,2,3] 实际上意味着 1 : 2 : 3 : [] 一样,上面的表达式实际上意味着 [1,2,3] >>= (\x -> return [x,x]),它又计算为 concat (map (\x -> [[x,x]]) [1,2,3])),得出 [1,1,2,2,3,3]

注意箭头是如何变成 >>= 和 lambda 的。这仅使用内置(在类型类中)的 Monad 函数,因此它通常适用于任何 Monad。

我们可以假装通过使用 (>>=) :: Monad m => m a -> (a -> m b) -> m b 并在我们提供的函数中使用 "extracted" a 来提取值,就像上面列表示例中的 lambda 一样。但是,实际上不可能以通用方式从 Monad 中获取值,这就是为什么 >>= 的 return 类型是 m b(在 Monad 中)

So what is (<-) exactly? Can it be replaced entirely by using extract? Is that the best way to desugar/circumvent it?

请注意,do 块 <-extract 的含义非常不同,即使对于同时具有 MonadComonad 实例的类型也是如此。例如,考虑 non-empty lists。他们有 Monad 的实例(这与列表的常用实例非常相似)和 Comonadextend/=>> 将函数应用于列表的所有后缀).如果我们写一个 do-block,例如...

import qualified Data.List.NonEmpty as N
import Data.List.NonEmpty (NonEmpty(..))
import Data.Function ((&))

alternating :: NonEmpty Integer
alternating = do
    x <- N.fromList [1..6]
    -x :| [x]

...x <- N.fromList [1..6]中的x代表非空列表的元素;然而,这个 x 必须 用于构建一个新列表(或者,更一般地说,用于设置一个新的单子计算)。正如其他人所解释的那样,这反映了 do-notation 是如何脱糖的。如果我们让脱糖后的代码看起来像原来的代码,就会变得更容易看:

alternating :: NonEmpty Integer
alternating =
    N.fromList [1..6] >>= \x ->
    -x :| [x]
GHCi> alternating
-1 :| [1,-2,2,-3,3,-4,4,-5,5,-6,6]

do 块中 x <- N.fromList [1..6] 下面的行相当于 lambda 的主体。 x <- 因此孤立起来类似于没有主体的 lambda,这没有任何意义。

另一个需要注意的重要事情是,上面的 do-block 中的 x 不对应任何一个 Integer,而是 all Integers 在列表中。这已经表明 <- 不对应于提取函数。 (对于其他单子,x 甚至可能根本不对应任何值,如 x <- Nothingx <- []。另见 。)

另一方面,extract 确实提取了单个值,没有 ifs 或 buts...

GHCi> extract (N.fromList [1..6])
1

... 然而,它实际上是一个 单个 值:列表的尾部被丢弃。如果我们要使用列表的后缀,需要extend/(=>>)...

GHCi> N.fromList [1..6] =>> product =>> sum
1956 :| [1236,516,156,36,6]

如果我们有一个共同标记(参见 this package 和其中的链接),上面的例子可能会被重写为:

-- codo introduces a function: x & f = f x
N.fromList [1..6] & codo xs -> do
    ys <- product xs
    sum ys

语句将对应于普通值;绑定变量(xsys),到共同值(在这种情况下,列出后缀)。这与我们使用 monadic do-blocks 的情况正好相反。总而言之,就您的问题而言,切换到 comonads 只是交换我们在计算上下文之外无法引用的内容。