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
的含义非常不同,即使对于同时具有 Monad
和 Comonad
实例的类型也是如此。例如,考虑 non-empty lists。他们有 Monad
的实例(这与列表的常用实例非常相似)和 Comonad
(extend
/=>>
将函数应用于列表的所有后缀).如果我们写一个 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 Integer
s 在列表中。这已经表明 <-
不对应于提取函数。 (对于其他单子,x
甚至可能根本不对应任何值,如 x <- Nothing
或 x <- []
。另见 。)
另一方面,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
语句将对应于普通值;绑定变量(xs
和 ys
),到共同值(在这种情况下,列出后缀)。这与我们使用 monadic do-blocks 的情况正好相反。总而言之,就您的问题而言,切换到 comonads 只是交换我们在计算上下文之外无法引用的内容。
所以我正在使用 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 usingextract
? Is that the best way to desugar/circumvent it?
请注意,do 块 <-
和 extract
的含义非常不同,即使对于同时具有 Monad
和 Comonad
实例的类型也是如此。例如,考虑 non-empty lists。他们有 Monad
的实例(这与列表的常用实例非常相似)和 Comonad
(extend
/=>>
将函数应用于列表的所有后缀).如果我们写一个 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 Integer
s 在列表中。这已经表明 <-
不对应于提取函数。 (对于其他单子,x
甚至可能根本不对应任何值,如 x <- Nothing
或 x <- []
。另见
另一方面,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
语句将对应于普通值;绑定变量(xs
和 ys
),到共同值(在这种情况下,列出后缀)。这与我们使用 monadic do-blocks 的情况正好相反。总而言之,就您的问题而言,切换到 comonads 只是交换我们在计算上下文之外无法引用的内容。