根据蒙纳德的自然变换,哈斯克尔(&lt ;-)

时间:2017-03-23 20:00:06

标签: haskell functional-programming monads syntactic-sugar comonad

所以我正在玩GHCi中的hasbolt模块,我对一些贬低有好奇心。我通过创建管道连接到Neo4j数据库,如下所示

ghci> pipe <- connect $ def {credentials}

这很好用。但是,我想知道(<-)运算符的类型是什么(GHCi不会告诉我)。最令人沮丧的解释描述了

do x <- a
   return x

desugars to

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和{{}}进行desugar {1}} / return没有它就无法运作。

理想情况下,最好只设置pure个实例以启用Comonad作为extract :: Monad m => m a -> a,但它让我觉得我不明白pipe = extract $ connect $ def {creds}

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

(<-)

因为不只是任何东西都可以用作自由变量。例如,您无法将管道绑定到(<-) :: a -> m b -> b 类型或Num。变量必须是&#34; String&#34; ish的东西,除了它实际上从来不是Bool;并且你绝对不能尝试绑定到String。因此,似乎它不是通常意义上的haskell函数(除非有一类函数从自由变量名称空间获取值...不太可能)。那么String究竟是什么?可以使用(<-)完全替换它吗?这是desugar /绕过它的最好方法吗?

3 个答案:

答案 0 :(得分:8)

  

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

<-没有类型,它是do符号语法的一部分,如您所知,它被转换为>>=和{的序列在一个名为desugaring的过程中{1}}。

  

但只是x&lt; - a ......?

这一行呢?

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

return

在ghci中工作的是repl是一种ghci> pipe <- connect $ def {credentials} 块;您可以将每个条目视为do函数中的一行(它比那更毛茸茸,但这是一个很好的近似值)。这就是为什么你需要(直到最近)在ghci中说main来声明绑定的原因。

答案 1 :(得分:4)

  

理想情况下,最好只使一个Comonad实例启用使用extract :: Monad m =&gt; m a - &gt; a as pipe = extract $ connect $ def {creds}但它让我感到烦恼,我不理解(&lt; - )。

Comonad与Monads无关。事实上,大多数Monads都没有任何有效的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 [] = ???

这告诉我们关于Monads的一些有趣的事情,即我们不能写出类型为Monad m => m a -> a的通用函数。换句话说,我们无法提取&#34;来自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,1,2,2,3,3]

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

我们可以假装使用(>>=) :: Monad m => m a -> (a -> m b) -> m b并使用&#34;提取的&#34;来提取值。我们提供的函数内部a,就像上面列表示例中的lambda一样。但是,实际上不可能以通用方式从Monad中获取值,这就是>>=的返回类型为m b(在Monad中)的原因

答案 2 :(得分:2)

  

那么(<-)究竟是什么?可以使用extract完全替换它吗?这是desugar /绕过它的最好方法吗?

请注意,即使对于同时包含<-extract个实例的类型,do-block 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中的x <- N.fromList [1..6]代表非空列表的元素;但是,此x 必须用于构建新列表(或更一般地,用于设置新的monadic计算)。正如其他人所解释的那样,这反映了如何使用符号。我们可以更容易地看到我们是否将脱落的代码看作原始代码:

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,这不是一件有意义的事情。

需要注意的另一个重要事项是,上面的do-block中的x与任何一个Integer都不对应,而是与所有 Integer相对应在列表中。这已经说明<-不对应于提取函数。 (对于其他monad,x甚至可能根本不对应任何值,例如x <- Nothingx <- []。另请参阅Lazersmoke's answer。)

另一方面,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的共同注释(参见this package及其中的链接),上面的示例可能会被重写为以下内容:

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

这些陈述与普通价值相对应;绑定变量(xsys),对于comonadic值(在本例中为列出后缀)。这与我们对monadic do-blocks的结果正好相反。总而言之,就您的问题而言,切换到comonads只是交换我们无法在计算环境之外引用的内容。