内容简介:系列文章然后来看看函数的类型IO () 返回的类型为
系列文章
- 《Haskell趣学指南》笔记之基本语法
- 《Haskell趣学指南》笔记之类型(type)
- 《Haskell趣学指南》笔记之函数
- 《Haskell趣学指南》笔记之高阶函数
- 《Haskell趣学指南》笔记之模块
- 《Haskell趣学指南》笔记之自定义类型
- 《Haskell趣学指南》笔记之I/O
Hello World
main = putStrLn "hello, world! ghc --make helloworld ./helloworld hello world
然后来看看函数的类型
ghci> :t putStrLn putStrLn :: String -> IO () ghci> :t putStrLn "hello, world" putStrLn "hello, world" :: IO () ghci> :k IO IO :: * -> * ghci> :k IO() IO() :: * 复制代码
IO () 返回的类型为 ()
,即 空元组
,也叫 单元
。下一节会出现的 IO String 返回的类型为 String。
()
即使一个类型,也是对应类型的值。
do 语法
do 语法可以将多个 I/O 操作合成一个。
main = do putStrLn "Hello, what' s your name?" name <- getLine putStrLn ("Hey " ++ name ++ ", you rock!") 复制代码
- getLine 的类型为 IO String
- getLine 是一个产生字符串的 I/O 操作
-
注意
name <-
并没有写成name =
- 只有在 I/O 操作的上下文中才能读取 I/O 操作的内容,这就是 Haskell 隔离『纯代码』和『不纯的代码』的方式
- do语法会自动将最后一个操作的值作为自己的返回值
IO String 与 String 的区别
nameTag = "Hello, my name is " ++ getLine 复制代码
++ 左边的类型是 String,右边的类型为 IO String,所以上面的代码会报错。必须通过 name <- getLine
取出这个 String,才能继续。只能在不纯的环境中处理不纯的数据。不然不纯的代码会像污水那样污染其余的代码,保持 I/ O 相关的代码尽可能小,这对我们的健康有好处。
myLine = getLine
如果代码写成了这样
myLine = getLine 复制代码
这只是给 getLine 增加了一个别名!从 I/O 操作获取值的唯一方法是使用 <-
。每次我们在 GHCi 中按下回车键,GHCi 都会对返回值应用 show,再将生成的字符串交给 putStrLn,从而输出到终端。
return 是不一样的
Haskell 的 return 跟其他语言不一样,return 能够基于一个纯的值来构造 I/O 操作。而且 return 不会中断代码。所以,在 I/O 上下文中,return "hi" 的类型就是 IO String。那么将一个纯值转换为一个什么都不做的 I/ O 操作又有什么意义呢?作用之一是 return ()
能够构建一个什么都不做的 I/O 操作。
几个 I/O 函数
-
putStr
-
putStr
-
print
(相当于 putStrLn . show) -
when <condition> $ do IO操作
-
sequence [getLine getLine]
用<-
取多个 I/O 操作的结果组成一个列表 -
mapM print [1, 2, 3]
等价于sequence $ map print [1, 2, 3]
-
mapM_
则是不保留返回值版本的mapM
-
forever $ do IO操作
- forM 则是把 mapM 的参数位置对换,某些时候比较方便
- getContents 从标准输入里读取所有的东西直到遇到一个 end-of-file 字符,而且 getContents 是惰性的
- interact fn 取一个类型为 String -> String 的函数 fn 作为参数,返回这样一个 I/ O 操作:接受输入,把一个函数作用在输入上,然后输出函数运行结果
- getArgs 获取命令行参数
- getProgName 获取程序名
读入文件流是随着时间连续地进入、离开程序的一组数据片。
-
创建文件 test.txt,内容如下
Hi! How are you? I'm Fine. Thank you. And you? I'm Fine too. 复制代码
-
创建文件 capslocker.hs,内容如下
import Control.Monad import Data.Char main = forever $ do l <- getLine putStrLn $ map toUpper l 复制代码
-
编译:运行
ghc --make capslocker
-
将 test.txt 的内容传给 capslocker:运行
./capslocker < test.txt
-
然后你就会看到所有字母变成了大写上面代码也能用 getContents 简化
import Data.Char main = do contents <- getContents putStr $ map toUpper contents 复制代码
也可以不传入 test.txt 文件,直接运行 ./capslocker,然后输入一行行文本,但是注意,最终你要按 Ctrl+D
来表示内容结束。
stdout 和 stdin
一种理解从终端读入数据的方式是设想我们在读取一个文件。输出到终端也可以同样理解——它就像在写文件。我们可以把这两个文件叫做 stdout 和 stdin,分别表示标准输出和标准输入。
用 openFile 打开文件
-
创建 gf.txt,内容如下:
Hey! Hey! You! You! I don' t like your girlfriend! No way! No way! I think you need a new one! 复制代码
-
创建 gf.hs,内容如下:
import System.IO main = do handle <- openFile "gf. txt" ReadMode contents <- hGetContents handle putStr contents hClose handle -- 注意 openFile ReadMode hGetContents hClose 这几个函数,其中的 h 前缀表示它接收 handle 复制代码
-
编译并运行如何知道 openFile 的各个参数的意思呢?
λ> :t openFile openFile :: FilePath -> IOMode -> IO Handle λ> :info FilePath type FilePath = String -- Defined in ‘GHC.IO’λ> :info IOMode data IOMode = ReadMode | WriteMode | AppendMode | ReadWriteMode -- Defined in ‘GHC.IO.IOMode’λ> :info Handle data Handle = GHC.IO.Handle.Types.FileHandle FilePath... 复制代码
用 withFile 打开文件
import System.IO main = do withFile "girlfriend.txt" ReadMode (\handle -> do contents <- hGetContents handle putStr contents) 复制代码
bracket 函数
bracket :: IO a -> (a -> IO b) -> (a -> IO c) -> IO c 复制代码
怎么用
bracket (openFile name mode)-- 打开文件 (\handle -> hClose handle) -- 失败了怎么办 (\handle -> fn handle) -- 成功了怎么办 复制代码
用 bracket 很容易实现 withFile。
生成随机数对于一个函数,如果两次调用它时使用相同的参数,它会把同样的结果返回两次。这很酷,因为它让我们能更好地理解程序,它还让我们能够延迟求值。但是,这也使得产生随机数这件事变成困难。
random :: (RandomGen g, Random a) => g -> (a, g) 复制代码
random 接受一个随机数生成器,返回一个随机数和一个新的随机数生成器。然后你可以用新的生成器再去生成一个新的随机数和一个新的生成器。以此类推。对于同一个生成器,得到的随机数是固定的。
ghci>import System.Random ghci> random (mkStdGen 100) :: (Int, StdGen) (-1352021624, 651872571 1655838864) ghci> random (mkStdGen 100) :: (Int, StdGen) (-1352021624, 651872571 1655838864) ghci> random (mkStdGen 949494) :: (Int, StdGen) (539963926, 466647808 1655838864) ghci> random (mkStdGen 949488) :: (Float, StdGen) (0. 8938442, 1597344447 1655838864) ghci> random (mkStdGen 949488) :: (Bool, StdGen) (False, 1485632275 40692) ghci> random (mkStdGen 949488) :: (Integer, StdGen) (1691547873, 1597344447 1655838864) 复制代码
randoms接受一个生成器,返回一个无限长的随机值列表
ghci> take 5 $ randoms (mkStdGen 11) :: [Int] [-1807975507, 545074951,- 1015194702,- 1622477312,- 502893664] 复制代码
并没有返回一个新的生成器,因为这个生成器在列表的末尾,而这个列表是无限长的……
randomR在一个范围内生成随机数
ghci> randomR (1, 6) (mkStdGen 359353) (6, 149428957840692) 复制代码
getStdGen
之前我们每次生成随机数都要自己先写一个数字,这很傻……所以 System.Random 提供了 getStdGen,它会向系统索要初始的全局生成器。但 getStdGen 是一个 IO 操作,它返回的类型是 IO stdGen。
import System.Random main = do gen <- getStdGen putStr $ take 20 (randomRs ('a',' z') gen) 复制代码
但是如果你调用 getStdGen 两次,你会获得同一个 gen。第二次应该使用 newStdGen。这个函数除了返回一个 IO stdGen 类型的值,还会把全局生成器给更新了。你再调用 getStdGen 就能得到不同的随机数这时你会疑惑,getStdGen 为什么能返回不同的结果呢?因为它是一个 I/O 操作!
import System.Random main = do gen <- getStdGen let a = fst ((random gen) :: (Int, StdGen)) print a gen' <- newStdGen let b = fst ((random gen') :: (Int, StdGen)) print b gen'' <- getStdGen let c = fst ((random gen'') :: (Int, StdGen)) print c print "end" 复制代码
这是我瞎写的代码。
字节串 bytestring
形如[ 1, 2, 3, 4] 的列表只是 1: 2: 3: 4:[] 的语法糖。当第一个元素被强制求值时(比如说输出它),列表的其余部分 2: 3: 4:[] 只是一个许诺将会产生列表的承诺。我们把这个承诺叫做 thunk。 thunk 大致上是一个延迟的计算。字节串有两种风格:严格的(strict)和惰性的(lazy)。
strict bytestring 废除了惰性,没有 thunk。在 Data.ByteString 中实现了。 lazy bytestring 是惰性的,但比列表效率高。在 Data.ByteString.Lazy 中实现了。惰性的字节串的数据存储在一些块(chunk)里(不要和 thunk 混淆了),每个块的大小是 64 KB。所以,如果你要对惰性的字节串的一个字节求值(比如说输出它),最开头的 64 KB 都会被求值。在那之后,其余块实现为一个承诺(thunk)。使用示例
import qualified Data. ByteString. Lazy as B import qualified Data. ByteString as S ghci> B. pack [99, 97, 110] Chunk "can" Empty ghci> B. pack [98.. 120] Chunk "bcdefghijklmnopqrstuvwx" Empty ghci> let by = B. pack [98, 111, 114, 116] ghci> by Chunk "bort" Empty ghci> B. unpack by [98, 111, 114, 116] ghci> B. fromChunks [S. pack [40, 41, 42], S. pack [43, 44, 45], S. pack [46, 47, 48]] Chunk "()*" (Chunk "+,-" (Chunk "./0" Empty)) ghci> B. cons 85 $ B. pack [80, 81, 82, 84] Chunk "U" (Chunk "PQRT" Empty) 复制代码
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- JavaScript权威指南笔记
- Elasticsearch权威指南学习笔记
- 《Haskell趣学指南》笔记之模块
- 《Haskell趣学指南》笔记之函数
- redis 运维和开发指南-学习笔记
- 《Haskell趣学指南》笔记之高阶函数
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
We Are the Nerds
Christine Lagorio-Chafkin / Hachette Books / 2018-10-2 / USD 18.30
Reddit hails itself as "the front page of the Internet." It's the third most-visited website in the United States--and yet, millions of Americans have no idea what it is. We Are the Nerds is an eng......一起来看看 《We Are the Nerds》 这本书的介绍吧!