我刚刚开始使用 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
的结果,根据 Pursuit,which does具有类型
Array String
。
我不明白这两种情况有什么不同。为什么
l
的类型在这两种情况下不同,我怎样才能使第二个代码示例工作?
“向左箭头”
<-
并不是您所假设的“变量赋值”。
相反,
<-
这个东西被称为“单子绑定”,它基本上意味着“在右侧运行计算并为其结果指定左侧的名称”。但重要的是“右侧计算”不仅仅是任何表达式。它必须是一个计算在与线出现的同一个单子中。
所以在你的例子中,由于
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 / 计算表达式及其语法吗