1,IO類型:跟IO相關(guān)的函數(shù)返回值放到IO類型容器里,避免跟pure函數(shù)混合
Haskell has a special parameterized type called IO. Any value in an IO context must stay in this context. This prevents code that’s pure (meaning it upholds referential transparency and doesn’t change state) and code that’s necessarily impure from mixing.
newtype IO a = GHC.Types.IO
Haskell solves this problem by forcing these two functions to be different types. Whenever a function uses IO, the results of that function are forever marked as coming from IO.
-- pure
mystery1 :: Int -> Int -> Int
mystery1 val1 val2 = (val1 + val2 + val3)^2
where val3 = 3
-- impure
mystery2 :: Int -> Int -> IO Int
mystery2 val1 val2 = do
putStrLn "Enter a number"
val3Input <- getLine
let val3 = read val3Input
return ((val1 + val2 + val3)^2)
Why does this IO type make your code safer? IO makes it impossible to accidentally use values that have been tainted with I/O in other, pure functions. For example, addition is a pure function, so you can add the results of two calls to mystery1:
safeValue = (mystery1 2 4) + (mystery1 5 6)
But if you try to do the same thing, you’ll get a compiler error:
unsafeValue = (mystery2 2 4) + (mystery2 2 4)
"No instance for (Num (IO Int)) arising from a use of '+'"
Maybe is a parameterized type (a type that takes another type as an argument) that represents a context when a value may be missing. IO in Haskell is a parameterized type that’s similar to Maybe.
The other thing that Maybe and IO have in common is that (unlike List or Map) they describe a context for their parameters rather than a container. The context for the IO type is that the value has come from an input/output operation. Common examples of this include reading user input, printing to standard out, and reading a file.
IO跟Maybe一樣定義一個(gè)context,包裝一個(gè)值,避免被包裝的值直接跟外界接觸
With a Maybe type, you’re creating a context for a single specific problem: sometimes a program’s values might not be there. With IO, you’re creating context for a wide range of issues that can happen with IO. Not only is IO prone to errors, but it’s also inherently stateful (writing a file changes something) and also often impure (calling getLine many times could easily yield a different result each time if the user enters different input). Although these may be issues in I/O, they’re also essential to the way I/O works. What good is a program that doesn’t change the state of the world in some way? To keep Haskell code pure and predictable, you use the IO type to provide a context for data that may not behave the way all of the rest of your Haskell code does. IO actions aren’t functions.
IO操作是有狀態(tài)的,所以要放到context中跟其他無(wú)狀態(tài)的部分隔離
2,解釋hello world
helloPerson :: String -> String
helloPerson name = "Hello" ++ " " ++ name ++ "!"
-- ()表示空的tuple
main :: IO ()
main = do
putStrLn "Hello! What's your name?"
name <- getLine
let statement = helloPerson name
putStrLn statement
At first () may seem like a special symbol, but in reality it’s just a tuple of zero elements. In the past, we’ve found tuples representing pairs or triples to be useful, but how can a tuple of zero elements be useful? Here are some similar types with Maybe so you can see that IO () is just IO parameterized with (), and can try to figure out why () might be useful:

What type does putStrLn return? It has sent a message out into the world, but it’s not clear that anything meaningful is going to come back. In a literal sense, putStrLn returns nothing at all. Because Haskell needs a type to associate with your main, but your main doesn’t return anything, you use the () tuple to parameterize your IO type. Because () is essentially nothing, this is the best way to convey this concept to Haskell’s type system.
putStrLn什么也不返回,()表示nothing,所以main的類型是IO ()
Although you may have satisfied Haskell’s type system, something else should be troubling you about your main. In the beginning of the book, we stressed three properties of functions that make functional programming so predictable and safe:
? All functions must take a value.
? All functions must return a value.
? Anytime the same argument is supplied, the same value must be returned (referential transparency).
Clearly, main doesn’t return any meaningful value; it simply performs an action. It turns out that main isn’t a function, because it breaks one of the fundamental rules of functions: it doesn’t return a value. Because of this, we refer to main as an IO action. IO actions work much like functions except they violate at least one of the three rules we established for functions. Some IO actions return no value, some take no input, and others don’t always return the same value given the same input.
main不返回值,違反了函數(shù)的三個(gè)特征,所以main不是函數(shù),main只是 IO action ?。。?/p>
3,IO actions
-- 沒(méi)有返回值
putStrLn :: String -> IO ()
-- 沒(méi)有參數(shù)
getLine :: IO String
-- 有參數(shù),也有返回值,但是相同的參數(shù)多次調(diào)用可能返回不同的值
import System.Random
minDie :: Int
minDie = 1
maxDie :: Int
maxDie = 6
main :: IO ()
main = do
-- 多次調(diào)用,返回不同值
dieRoll <- randomRIO (minDie, maxDie)
putStrLn (show dieRoll)
Because I/O is so dangerous and unpredictable, after you have a value come from I/O, Haskell doesn’t allow you to use that value outside of the context of the IO type. For example, if you fetch a random number using randomRIO, you can’t use that value outside main or a similar IO action. You’ll recall that with Maybe you could use pattern matching to take a value safely out of the context that it might be missing. This is because only one thing can go wrong with a Maybe type: the value is Nothing. With I/O, an endless variety of problems could occur. Because of this, after you’re working with data in the context of IO, it must stay there.
IO出錯(cuò)的原因太多,所以IO actions無(wú)法超出IO(unsafe,stateful)范圍。也即是函數(shù)中無(wú)法出現(xiàn)IO actions ?。。?/p>
4,do:do范圍內(nèi)的表達(dá)式可以把IO類型當(dāng)作普通類型
This do-notation allows you to treat IO types as if they were regular types. This also explains why some variables use let and others use <-. Variables assigned with <- allow you to act as though a type IO a is just of type a. You use let statements whenever you create variables that aren’t IO types.
<-賦值的變量可以直接把 a 從 IO a 中取出來(lái)
let語(yǔ)句用于給非IO變量賦值

5,獲取命令行參數(shù)
import System.Environment
import Control.Monad
main :: IO ()
main = do
-- 獲取命令行參數(shù)
args <- getArgs
let linesToRead = if length args > 0
then read (head args)
else 0 :: Int
-- 重復(fù)某個(gè)IO action n次
numbers <- replicateM linesToRead getLine
let ints = map read numbers :: [Int]
print (sum ints)

6,lazy I/O
toInts :: String -> [Int]
toInts = map read . lines
main :: IO ()
main = do
-- getContents把輸入流看作一個(gè)lazy list,直到遇到end
userInput <- getContents
let numbers = toInts userInput
print (sum numbers)
7,Text類型:性能比String好,通過(guò)pack把String轉(zhuǎn)成Text,unpack把Text轉(zhuǎn)成String
8,文件讀寫(xiě)
import System.IO
-- 定義
-- openFile :: FilePath -> IOMode -> IO Handle
-- type FilePath = String
-- data IOMode = ReadMode | WriteMode | AppendMode | ReadWriteMode
main :: IO ()
main = do
-- 打開(kāi)文件
helloFile <- openFile "hello.txt" ReadMode
-- 讀文件
firstLine <- hGetLine helloFile
putStrLn firstLine
secondLine <- hGetLine helloFile
goodbyeFile <- openFile "goodbye.txt" WriteMode
-- 寫(xiě)文件
hPutStrLn goodbyeFile secondLine
-- 關(guān)閉文件
hClose helloFile
hClose goodbyeFile
putStrLn "done!"