输入输出#

编译#

$ ghc [options] file ...
$ runhaskell [options] file ...
$ runghc [options] file ...
  • GHC 编译器可对 Haskell 文件进行编译,生成中间文件和可执行文件;
  • runhaskell命令为 GHC 自带,可实时编译 Haskell 文件,且不会生成任何文件;
  • runghc命令是runhaskell命令的同义词;

输出#

  • Haskell 是无副作用的,即不改变对象的状态,为了和外界交互,Haskell 提供了一套处理有副作用函数的系统;
System.IO.putStrLn :: String -> IO ()#

将字符串输出到标准输出,并在末尾添加换行符。

IO类型类为 I/O 相关类型类,接受一个类型参数a,该类在执行时先执行输入输出操作(有副作用,通常为从键盘获得值或将值输出到屏幕),再返回类型为a的值(类似于返回值)。

IO类型类只能在main变量中、其他 I/O 语句中或 GHCi 环境中执行,因此main变量的类型始终为IO <type>,所以通常不会声明main变量的类型。

main :: IO ()
main = putStrLn "Hello, world!"
$ ghc --make Test
[1 of 1] Compiling Main             ( Test.hs, Test.o )
Linking Test ...
$ ./Test
Hello, world!

输入#

System.IO.getLine :: IO String#

getLine函数类型声明为IO String,表示从键盘读取一行字符串并返回结果为IO String,不包括换行符。

<-IO类型类执行的结果与变量绑定,会将IO <type>类型转换为<type>(取出具体类型)并与变量绑定。

<-可将有副作用和无副作用的代码分隔,对putStrLn同样有效。

Prelude> getLine
alice
"alice"

do语句块#

do <IO action>; ...
  • do语句块将多个 I/O 语句结合在一起,从上至下执行(类似命令式语言);

  • do语句块最后一个语句不能使用<-绑定变量,因为do语句块会从最后一个语句获得返回值并将其绑定在自己的变量上;

  • 单行书写时,语句之间用分号分隔,多行书写不用分号;

  • let语句同样适用于do语句块,且in语句可省略;

     1import qualified Data.Char as Ch
     2
     3main :: IO ()
     4main = do
     5    putStrLn "What is your first name?"
     6    firstName <- getLine
     7    putStrLn "What is your last name?"
     8    lastName <- getLine
     9    let upperFirstName = map Ch.toUpper firstName
    10        upperLastName  = map Ch.toUpper lastName
    11    putStrLn $ "Hello " ++ upperFirstName ++ " " ++ upperLastName
    
  • do语句块实际上是单子的语法糖

main :: IO ()
main = do
    putStrLn "What is your name?"
    name <- getLine
    putStrLn $ "Helle, " ++ name ++ "!"

return函数#

return :: Monad m => a -> m a#
  • return函数与命令式语言中的return关键字不同,表示将参数返回为单子而值不变;
  • return函数在 I/O 中无实际意义,通常用于创建IO类型类的返回值;
 1-- | 将每个单词反序输出。
 2main :: IO ()
 3main = do
 4    line <- getLine
 5    if null line
 6        then return () -- if 语句具有相同返回值
 7        else do
 8            putStrLn $ reverseWords line
 9            main       -- 递归调用
10
11-- | 反序输出字符串中的每个单词。
12reverseWords :: String -> String
13reverseWords = unwords . map reverse . words

I/O 函数#

输出函数#

System.IO.putStr :: String -> IO ()#

将字符串写入标准输出,但没有换行符。

main :: IO ()
main = do
    putStr "Hey, "
    putStr "I'm "
    putStrLn "Andy!" -- Hey, I'm Andy!
System.IO.putChar :: Char -> IO ()#

将字符写入标准输出,putStr函数即根据该函数定义。

putStr' :: String -> IO ()
putStr' [] = return ()
putStr' (x : xs) = do
    putChar x
    putStr' xs
System.IO.print :: Show a => a -> IO ()#

接受类型为Show类型类成员的值,对值调用show函数并写入标准输出,等价于putStrLn . show,是 GHCi 输出结果时默认调用的函数。

main :: IO ()
main = do
    print True      -- True
    print "Alice"   -- "Alice"
    print 2         -- 2
    print [2, 3, 4] -- [2,3,4]

输入函数#

System.IO.getChar :: IO Char#

从键盘读取一个字符。

main :: IO ()
main = do
    c <- getChar
    if c /= ' '
        then do
            putChar c
            main
        else return ()

控制函数#

Control.Monad.when :: Applicative f => Bool -> f () -> f ()#

接受一个布尔值和 I/O 操作,若为True则返回该操作,否则返回return (),适用于简化if语句。

import Control.Monad

-- | 上一个 'getChar' 函数的重写。
main :: IO ()
main = do
    c <- getChar
    when (c /= ' ') $ do
        putChar c
        main
Data.Traversable.sequence :: (Traversable t, Monad m) => t (m a) -> m (t a)#

接受 I/O 操作列表并按顺序执行操作,返回值为已执行 I/O 操作返回结果的列表。

main :: IO ()
main = do                                      -- alice
    rs <- sequence [getLine, getLine, getLine] -- in
    print rs                                   -- wonderland
    -- ["alice","in","wonderland"]
Data.Traversable.mapM :: (Traversable t, Monad m) => (a -> m b) -> t a -> m (t b)#

将函数对列表进行映射后,按顺序执行操作,等价于sequence (map f xs)

main :: IO ()
main = mapM print [1..3]
     -- 1
     -- 2
     -- 3
     -- [(),(),()]
Data.Foldable.mapM_ :: (Foldable t, Monad m) => (a -> m b) -> t a -> m ()#

mapM函数相似,但丢弃返回结果。

main :: IO ()
main = mapM_ print [1..3]
     -- 1
     -- 2
     -- 3
Control.Monad.forever :: Applicative f => f a -> f b#

接受一个 I/O 操作并无限重复该操作。

import Control.Monad
import Data.Char

main :: IO ()
main = forever $ do
    putStr "Give me some input: "
    l <- getLine             -- Give me some input: alice
    putStrLn $ map toUpper l -- ALICE
Control.Monad.forM :: (Traversable t, Monad m) => t a -> (a -> m b) -> m (t b)#

类似于mapM函数,但形参顺序相反。

 1import Control.Monad
 2
 3main :: IO ()
 4main = do
 5    colors <- forM
 6        [1..4]
 7        (\a -> do
 8            putStrLn $ "Which color do you bind with " ++ show a
 9            color <- getLine
10            return color
11        )
12    putStrLn "The colors you associate with 1, 2, 3, and 4 are:"
13    mapM_ putStrLn colors
Control.Monad.forM_ :: (Foldable t, Monad m) => t a -> (a -> m b) -> m ()#

forM函数类似,但丢弃返回结果。

随机#

备注

Haskell 中,相同参数调用相同函数,得到的一定是相同结果,为了获得(伪)随机数,需要System.Random模块。

自 GHC 7.2.1 之后,GHC 便不再包含System.Random模块,因此需要自行下载。

System.Random.random :: (Random a, RandomGen g) => g -> (a, g)#

根据随机种子返回随机数和新的随机种子。

Random类型类表示可成为随机值的数据,默认包含所有基本类型和Word类;

RandomGen类型类表示可成为随机种子的数据。

random*函数可使用类型注释改变随机值的类型。

用相同的参数调用函数只能得到相同的结果,因此需要新的随机种子生成新的随机值。

System.Random.mkStdGen :: Int -> StdGen#

根据整数生成随机种子。

StdGen类型是RandomGen类型类的成员。

import System.Random

exp1 = mkStdGen 100
-- StdGen {unStdGen = SMGen 16626... 25326...}
exp2 = random $ mkStdGen 100
-- (92164...,StdGen {unStdGen = SMGen 71263... 25326...})
exp3 = fst (random (mkStdGen 100) :: (Bool, StdGen)) -- True
exp4 = fst . random . mkStdGen $ 1 -- -2241774542048937483
exp5 = fst . random . mkStdGen $ 1 -- -2241774542048937483
System.Random.randoms :: (Random a, RandomGen g) => g -> [a]#

根据随机种子不断生成随机值。

exp6 = take 5 $ randoms (mkStdGen 11) :: [Bool]
       -- [True,True,False,False,False]
System.Random.randomR :: (Random a, RandomGen g) => (a, a) -> g -> (a, g)#

根据随机种子生成一个在序对规定范围内(闭区间)的随机值。

exp7 = fst $ randomR (1,6) (mkStdGen 233333) -- 6
System.Random.randomRs :: (Random a, RandomGen g) => (a, a) -> g -> [a]#

randomR类似,但生成无限个随机值。

exp8 = take 3 $ randomRs (1, 6) (mkStdGen 233333) -- [6,2,3]
exp9 = take 8 $ randomRs ('a', 'z') (mkStdGen 20) -- "itxegwun"
System.Random.getStdGen :: MonadIO m => m StdGen#

获得全局随机种子。

Haskell 启动程序时,会从系统处获得随机种子并储存在全局随机种子中,同一程序中全局随机种子不变。

MonadIO类型类定义于模块Control.Monad.IO.Class

import System.Random

main :: IO ()
main = do
    gen <- getStdGen
    putStr $ take 20 (randomRs ('a', 'z') gen)
System.Random.newStdGen :: MonadIO m => m StdGen#

将全局随机种子分为两份,用其中一份更新全局随机种子,返回另一个。

import System.Random

main :: IO ()
main = do
    gen <- getStdGen
    putStr $ take 20 (randomRs ('a', 'z') gen)
    newgen <- newStdGen
    putStr $ take 20 (randomRs ('a', 'z') newgen)
 1module Main where
 2
 3import           Control.Monad                  ( when )
 4import           System.Random                  ( Random(randomR)
 5                                                , StdGen
 6                                                , newStdGen
 7                                                )
 8
 9main :: IO ()
10main = do
11    newGen <- newStdGen
12    let num = getNum newGen
13    gameLoop num
14
15-- | 游戏循环。
16gameLoop :: Int -> IO ()
17gameLoop n = do
18    putStrLn "Guess a number: "
19    guess <- getLine
20    let (code, msg) = putRes (read guess) n
21    putStrLn msg
22    when code (gameLoop n)
23
24-- | 生成随机数。
25getNum :: StdGen -> Int
26getNum = fst . randomR (1, 100)
27
28-- | 判断猜测是否正确。
29putRes :: Int -> Int -> (Bool, String)
30putRes g n | g > n     = (True, "Too big!")
31           | g < n     = (True, "Too small!")
32           | otherwise = (False, "Exactly!")