对 do 表示法感到困惑

问题描述 投票:0回答:1

我刚刚开始使用 Purescript,很快就遇到了障碍。考虑下面这个简单(而且非常人为)的例子:

module Main where

import Prelude

import Data.String.Common (joinWith)
import Data.String.Utils (lines)
import Effect (Effect)
import Effect.Console (log)

contents :: String
contents = "1 2\n3 4"

l :: Array String
l = lines contents

main :: Effect Unit
main = do
  log (joinWith "\n" l)

简单的东西:使用

lines
函数创建一个字符串数组,使用
joinWith
将其连接回单个字符串,将其显示在控制台上。正如预期的那样,它编译并输出了

1 2
3 4

现在,我用“do notation”重写它,这似乎相当于我:

module Main where

import Prelude

import Data.String.Common (joinWith)
import Data.String.Utils (lines)
import Effect (Effect)
import Effect.Console (log)

contents :: String
contents = "1 2\n3 4"

main = do
  l <- lines contents
  log (joinWith "\n" l)

我刚刚将

l
移到
do
块内,所以在我看来,它应该是等效的......但是,Purescript 编译器不同意:

Could not match type
          
    String
          
  with type
                
    Array String
                

while checking that type String
  is at least as general as type Array String
while checking that expression l
  has type Array String
in value declaration main

如果我正确地阅读了此错误消息,则意味着现在

l
的类型为
String
,而在原始代码中(其中
l
do
块之外),它的类型为
Array String
。然而,在这两种情况下,它都会收到
split
的结果,根据 Pursuitwhich does
 具有类型 
Array String

我不明白这两种情况有什么不同。为什么

l
的类型在这两种情况下不同,我怎样才能使第二个代码示例工作?

purescript
1个回答
0
投票

“向左箭头”

<-
并不是您所假设的“变量赋值”。

相反,

<-
这个东西被称为“单子绑定”,它基本上意味着“在右侧运行计算并为其结果指定左侧的名称”。但重要的是“右侧计算”不仅仅是任何表达式。它必须是一个计算在与线出现的同一个单子中

所以在你的例子中,由于

main :: Effect Unit
do
符号在
Effect
单子中“运行”,因此,
<-
箭头右侧的东西也必须是
Effect
。该效果将被执行,其结果将被命名为
l

但是表达式

lines contents
不是
Effect
!这是一个
Array
。所以整个事情都无法计算。

要为值命名(该值不是同一 monad 中的计算),请使用

let
而不是
<-
箭头:

main :: Effect Unit
main = do
  let l = lines content
  log (joinWith "\n" l)

但是你通过删除

main :: Effect Unit
类型签名使事情变得有点复杂,我只能假设这是试图将该死的东西硬塞进去工作。这是一个错误,因为它移动了错误并使错误变得不那么明显。

问题是,

Effect
并不是唯一适用于
do
表示法的东西。用花哨的术语来说,这不是唯一的 monad。其中有很多。如果你问我的话,太多了。

特别是,

Array
也是一个单子。惊喜!因此
do
表示法也适用于数组。比如:

squares :: Array Int
squares = do
  n <- 1..10
  pure (n * n)

因此,由于您的

main
没有类型签名来告诉编译器它应该是
Effect
,并且它的第一行试图
<-
绑定类型为
Array String
的表达式,编译器认为您的意思是整个事情都在
Array
monad 中运行,并且很高兴遵守了。

由于现在整个事情都在

Array
monad 中,并且
<-
箭头右侧的表达式是
Array String
类型,所以它的“结果”就是
String
,所以这个
l
的类型必须是什么。

从那里你会得到

joinWith
的错误,因为它需要一个
Array String
类型的参数,但你只给了它
String

经验教训:使用类型签名告诉编译器您期望的东西是什么。否则它会得出自己的结论。


您可能也有兴趣阅读此解释:有人可以在 F# 中澄清 monad / 计算表达式及其语法吗

© www.soinside.com 2019 - 2024. All rights reserved.