输入输出#
编译#
$ 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!
输入#
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#
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
随机#
备注
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!")