Hello World
一个基本的 Hello World!
Haskell 中的程序可以用一两行简洁地表达出来:
main::IO ()
main = putStrLn "Hello, World!"
第一行是可选类型注释,表示 main
是 IO ()
类型的值,表示 I / O 动作计算()
类型的值(读取 unit
;空元组不传达信息)除了执行某些操作对外界的副作用(这里,在终端打印一个字符串)。main
通常省略此类型注释,因为它是唯一可能的类型。
将它放入 helloworld.hs
文件并使用 Haskell 编译器(如 GHC)编译它:
ghc helloworld.hs
执行编译的文件将导致输出 Hello, World!
被打印到屏幕:
./helloworld
Hello, World!
或者,runhaskell
或 runghc
使得可以在解释模式下运行程序而无需编译它:
runhaskell helloworld.hs
也可以使用交互式 REPL 而不是编译。它随大多数 Haskell 环境一起提供,例如 GHC 编译器附带的 ghci
:
ghci> putStrLn "Hello World!"
Hello, World!
ghci>
或者,使用 load
(或:l
)从文件加载脚本到 ghci:
ghci> :load helloworld
:reload
(或:r
)重新加载 ghci 中的所有内容:
Prelude> :l helloworld.hs
[1 of 1] Compiling Main ( helloworld.hs, interpreted )
<some time later after some edits>
*Main> :r
Ok, modules loaded: Main.
说明:
第一行是类型签名,声明了 main
的类型:
main::IO ()
IO ()
类型的值描述了可以与外部世界交互的行为。
因为 Haskell 有一个完全成熟的 Hindley-Milner 类型系统允许自动类型推断,类型签名在技术上是可选的:如果你只是省略 main::IO ()
,编译器将能够通过分析 main
的定义自己推断出类型。但是,如果不为顶级定义编写类型签名,则被认为是不好的样式。原因包括:
-
Haskell 中的类型签名是一个非常有用的文档,因为类型系统是如此富有表现力,以至于你通常只需查看其类型就可以看到函数有什么好处。可以使用 GHCi 等工具方便地访问此文档。与普通文档不同,编译器的类型检查器将确保它实际匹配函数定义!
-
类型签名将 bug 保持为本地。如果你在定义中犯了一个错误而没有提供它的类型签名,编译器可能不会立即报告错误,而是简单地为它推断出一个无意义的类型,实际上它可以用来检测它。然后,你可能会在使用该值时收到一条神秘的错误消息。通过签名,编译器非常善于发现错误。
第二行是实际工作:
main = putStrLn "Hello, World!"
如果你来自命令式语言,请注意此定义也可以写成:
main = do {
putStrLn "Hello, World!" ;
return ()
}
或者等价(Haskell 具有基于布局的解析;但要注意混合制表符和空格不一致会混淆这种机制):
main = do
putStrLn "Hello, World!"
return ()
在一个 do
块的每一行代表一些一元 (在此,I / O) 计算,使得整个 do
块表示通过将它们在具体给定的单子的方式结合由这些子步骤的总体操作(I / O 这意味着一个接一个地执行它们。
do
语法本身就是 monad 的语法糖,就像 IO
这里一样,return
是一个无操作动作,产生它的参数而不会执行任何副作用或额外的计算,这可能是特定 monad 定义的一部分。
以上与定义 main = putStrLn "Hello, World!"
相同,因为值 putStrLn "Hello, World!"
已经具有 IO ()
类型。作为一个声明,putStrLn "Hello, World!"
可以看作是一个完整的程序,你只需定义 main
来引用这个程序。
你可以在线查看 putStrLn
的签名 :
putStrLn::String -> IO ()
-- thus,
putStrLn (v::String) :: IO ()
putStrLn
是一个函数,它接受一个字符串作为参数并输出一个 I / O 动作(即表示运行时可以执行的程序的值)。运行时始终执行名为 main
的操作,因此我们只需将其定义为等于 putStrLn "Hello, World!"
。