2

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

ghci> pipe <- connect $ def {credentials}

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

do x <- a
   return x

脱糖

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

但是只有这条线呢x <- a?添加它对我没有帮助,return因为我不想pipe :: Pipepipe :: Control.Monad.IO.Class.MonadIO m => m Pipe但是如果没有它,(>>=) :: Monad m => m a -> (a -> m b) -> m b尝试使用bindand return/来脱糖pure是行不通的。

理想情况下,似乎最好只创建一个Comonad实例以启用extract :: Monad m => m a -> aaspipe = extract $ connect $ def {creds}但它让我不明白(<-)

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

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

因为不仅仅是任何东西都可以用作自由变量。例如,您不能将管道绑定到Num类型或Bool. 变量必须是一个“字符串”的东西,除非它实际上不是一个String; 而且您绝对不能尝试实际绑定到String. 所以它似乎不是通常意义上的haskell函数(除非有一类函数从自由变量命名空间中获取值......不太可能)。那么究竟是(<-)什么?可以用 using 完全替换extract吗?这是脱糖/规避它的最佳方法吗?

4

3 回答 3

9

我想知道 (<-) 运算符的类型是什么......

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

但是只有行 x <- a ...呢?

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

ghci> pipe <- connect $ def {credentials}

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

于 2017-03-23T20:15:47.880 回答
4

理想情况下,最好只制作一个 Comonad 实例以启用使用 extract :: Monad m => ma -> a as pipe = extract $ connect $ def {creds} 但这让我不明白(<-)。

Comonad 与 Monads 无关。事实上,大多数 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 的一些有趣的事情,即我们不能用 type 编写通用函数Monad m => m a -> a。换句话说,我们不能在没有额外知识的情况下从 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]

请注意箭头如何转换为 a>>=和 lambda。这仅使用内置(在类型类中)Monad 函数,因此它通常适用于任何 Monad。

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

于 2017-03-23T21:49:30.283 回答
2

那么究竟是(<-)什么?可以用 using 完全替换extract吗?这是脱糖/规避它的最佳方法吗?

请注意,即使对于同时具有和实例的类型,do-block<-extract意味着非常不同的东西。例如,考虑非空列表。它们有两个实例(这与列表的常用实例非常相似)和(使用/将函数应用于列表的所有后缀)。如果我们写一个do-block,比如......MonadComonadMonadComonadextend=>>

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]

... xinx <- N.fromList [1..6]代表非空列表的元素;但是,这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-block下面的行x <- N.fromList [1..6]相当于 lambda 的主体。x <-因此,孤立地类似于没有主体的 lambda,这不是有意义的事情。

另一个需要注意的重要事情是,x在上面的 do-block 中不对应任何一个 single Integer,而是对应于列表中的所有 Integers。这已经放弃了<-与提取功能不对应的内容。(对于其他单子, thex甚至可能根本不对应任何值,如x <- Nothingor x <- []。另请参阅Lazersmoke 的答案。)

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

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

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

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

如果我们有一个 comonads 的共同表示法(参见这个包和其中的链接),上面的例子可能会被重写为以下内容:

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

这些语句将对应于普通值;绑定变量 (xsys) 到 comonadic 值(在这种情况下,列出后缀)。这与我们使用单子 do-blocks 的情况完全相反。总而言之,就您的问题而言,切换到comonads只是交换了我们在计算上下文之外无法引用的东西。

于 2017-03-24T04:18:03.843 回答