1,IO类型:跟IO相关的函数返回值放到IO类型容器里,避免跟pure函数混合
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一样定义一个context,包装一个值,避免被包装的值直接跟外界接触
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操作是有状态的,所以要放到context中跟其他无状态的部分隔离
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不返回值,违反了函数的三个特征,所以main不是函数,main只是 IO action !!!
3,IO actions
-- 没有返回值
putStrLn :: String -> IO ()
-- 没有参数
getLine :: IO String
-- 有参数,也有返回值,但是相同的参数多次调用可能返回不同的值
import System.Random
minDie :: Int
minDie = 1
maxDie :: Int
maxDie = 6
main :: IO ()
main = do
-- 多次调用,返回不同值
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出错的原因太多,所以IO actions无法超出IO(unsafe,stateful)范围。也即是函数中无法出现IO actions !!!
4,do:do范围内的表达式可以把IO类型当作普通类型
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 中取出来
let语句用于给非IO变量赋值
5,获取命令行参数
import System.Environment
import Control.Monad
main :: IO ()
main = do
-- 获取命令行参数
args <- getArgs
let linesToRead = if length args > 0
then read (head args)
else 0 :: Int
-- 重复某个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把输入流看作一个lazy list,直到遇到end
userInput <- getContents
let numbers = toInts userInput
print (sum numbers)
7,Text类型:性能比String好,通过pack把String转成Text,unpack把Text转成String
8,文件读写
import System.IO
-- 定义
-- openFile :: FilePath -> IOMode -> IO Handle
-- type FilePath = String
-- data IOMode = ReadMode | WriteMode | AppendMode | ReadWriteMode
main :: IO ()
main = do
-- 打开文件
helloFile <- openFile "hello.txt" ReadMode
-- 读文件
firstLine <- hGetLine helloFile
putStrLn firstLine
secondLine <- hGetLine helloFile
goodbyeFile <- openFile "goodbye.txt" WriteMode
-- 写文件
hPutStrLn goodbyeFile secondLine
-- 关闭文件
hClose helloFile
hClose goodbyeFile
putStrLn "done!"