Haskell 的 IO 和Monad

Haskell 的 IO 和Monad

我们在 Haskell 中所做的一切都是“独立的”。我们要么将数据包含在我们的 .hs 文件或在解释器提示符处将其作为函数参数输入。
• 与外界的互动如何? – 文件、图形用户界面、图形?
• 您如何做到这一点而不产生副作用?
• 读入变量的值是状态的改变。
• 如果您像在 java 中那样逐行阅读文件:
str = rdr.readString()
那么每次你这样做时,你都会破坏性地覆盖 str 的值
• 如果您允许在函数体内进行这样的操作,那么您永远无法确定给定相同的参数,该函数将始终返回相同的值。
• 交互通常按顺序发生,尽管在功能上没有直接的方式编程以指定“做 x 然后 y 然后 z”。

我们还想使用 Haskell 编写交互式程序,在它们运行时从键盘读取并写入屏幕。
在这里插入图片描述
问题
• Haskell 程序是纯数学函数:Haskell 程序没有副作用。
• 从键盘读取和写入屏幕是副作用:交互式程序有副作用

Haskell 中的 IO
Haskell 强制在处理 i/o 的函数之间进行清晰的分离,允许“纯粹”的动作和功能,它们是定义。
• Haskell 提供类型
IO a
• 具有此返回类型的对象是将执行一些 IO 的程序
然后返回一个 a 类型的值。 IO 可能是单个操作或
动作的数量。
• Haskell 为 IO 操作提供了一种语言,包括一种排序方式这钟动作。
• 您只能在 IO 类型的函数中使用该语言
IO Char :动作类型是返回一个字符
IO ():纯边的类型影响的行动这个不返回结果值
注意: () 是没有组件的元组类型

基本动作
• 标准库提供了几个动作,包括以下三个原语:
• getChar 操作从键盘读取一个字符,将其回显到
屏幕,并返回字符作为其结果值:
getChar :: IO Char
• putChar c 操作将字符 c 写入屏幕,并返回 no
结果值:
putChar :: Char -> IO ()
• 动作return v 只返回值v,不执行任何操作和相互作用:
return :: a -> IO a

终端交互
• 更多 IO 命令:
getLine :: IO String – 读取一行,将其作为字符串返回
putStr :: String->IO () – 打印一个字符串。返回类型为“无效”(‘void’)
• 可用于返回类型为 IO a 的函数中

sayHello :: IO ()
sayHello = putStrLn "Hello world" 

sayHello
Hello world

那不是交互!
• do 允许在 IO 函数中使用序列:

greeting :: IO ()
greeting = do
	putStrLn "Greetings! What is your name?"
	inpStr <- getLine
	putStrLn ("Welcome to Haskell, " ++ inpStr ++ "!")

• 注意:评估一个动作会执行其副作用,并得出最终结果值被丢弃。

干净利落的休息
• 您只能在返回类型为 IO a 的函数内使用 do
• 您只能在 IO a 的函数中使用返回类型为 IO a 的现有函数(例如 getLine)
• 在do 中,<- 将变量绑定到IO 操作的结果
• 为了在纯函数和 IO 之间架起一座“桥梁”,我们使用 return,例如:

getLine' :: IO String
getLine' = do x <- getChar
	if x == '\n' then
		return []
	else
		do xs <- getLine'
			return (x:xs)

另一座"桥梁"
您可以在 do 中调用“纯”函数,但您可以使用 let 来绑定他们返回的值

takesome :: IO ()
takesome = do
	putStrLn " give me a string please"
	listr<-getLine
	putStrLn " and an index"
	istr<-getLine
	let resstr = (take (read istr ::Int) listr)
	putStrLn $ " the first " ++ istr ++" chars in your string are "++resstr

这里在终端输入的String可以无引号,如果有引号在这里引号也会算作一个char
istr 是一个字符串;使用读取,将其转换为 Int

其他 I/O 操作
• 我们只查看了终端 IO (getLine, putStrLn) 但相同
想法通过文件以明显的方式扩展到 IO,例如
inpStr <- hGetLine inh
从您打开的文件中读取字符串。
• inh 是该文件的“句柄”,当您打开它时会得到它:
inh <- openFile “input.txt” 读取模式
• Haskell 将“惰性求值”扩展到 IO,例如,如果您要求读取整个文件 Haskell 仅在需要时从其中读取数据。

另一种处理错误的方法

module IOdemo where
	import Control.Exception
	import System.IO
	import System.IO.Error
getAndOpenFile :: String -> IOMode -> IO Handle
getAndOpenFile prompt mode =
	do putStr prompt
		name <- getLine
		catchIOError (openFile name mode)
			(\_ -> do putStrLn ("Cannot open"++ name ++ "\n")
			getAndOpenFile prompt mode)
main = do fromHandle <- getAndOpenFile "Copy from: " ReadMode
	toHandle <- getAndOpenFile "Copy to: " WriteMode
	contents <- hGetContents fromHandle
	hPutStr toHandle contents
	hClose toHandle
	putStr "Done."

概括
• Haskell 封装了 I/O 的混乱
• 本质上,IO 函数隐含地将“世界状态”作为输入并返回新的世界状态作为输出的一部分
• I/O 常见异常
• 这引入了

  1. List item

一种新的错误处理方式,使用 catch

IO只是 Monad 类的一个实例

什么是单子monad ?
“monad 是一种根据值和使用这些值的计算序列。单子允许程序员使用顺序构建块建立计算,它本身可以是计算序列。单子确定组合计算如何形成新的计算和使程序员不必手动编写组合代码每次都需要。 ”
换句话说,更多隐藏了混乱
• Monads 允许排序sequencing
• Monads 允许绑定binding(cough cough 有点像变量)
• 但他们encapsulate 封装了这些东西
• 保持“纯”位纯洁

Monad 是一个类
• 记住 Eq 和 Ord,以及此类的实例必须如何定义某些功能?
• Monad 类需要这些函数:
• bind (>>=)
• then (>>)
• return
• fail

你已经在上面看到了return 。所以如果 IO 是Monad的一个实例,为什么我们没有看到其他三个?

绑定bind (>>=)
• 运算符 >>=(读作 bind)接受 Monad 和转换函数并基本上返回该函数的应用功能。
• 为什么本质上?好吧,它有点复杂。让我们看一个单子的例子来解释……

也许Maybe
• Maybe 是 Monad。换句话说,它实现了所有这四个功能
• 现在让我们关注绑定函数。它是这样定义的:

data Maybe a = Nothing | Just a
instance Monad Maybe where
	(Just x) >>= k = k x
	Nothing >>= _ = Nothing

请注意,在 bind 函数中,它没有将函数 k 应用于
Maybe 类型,但是对于 Maybe 类型中的东西(所以如果它是 Maybe Int,它会将 k 应用于 Int)
• k 的返回类型也必须是 Maybe 类型,但也可以是 Maybe其他类型

maybeHalf :: Int -> Maybe Int
maybeHalf a
	| even a = Just (div a 2)
	| otherwise = Nothing

如果是偶数m就会输出just m/2
如果是奇数n 就会输出Nothing

然后 Then(>>)
• Then >> 运算符(读作 then)与 bind 类似,但它与Monad 中包含的值无关。
• 这有什么意义?Monad 容器隐含地携带有关状态的信息。 Maybe 类型不做任何事情此信息,但其他 Monad 类型可以。
• 例如,我们上周看到的 IO Monad 带有隐含的
有关文件位置之类的信息(从文件中读取时)

do是缩写

main = do putStr "What is your name?”
	a <- readLn
	putStr "How old are you?”
	b <- readLn print (a,b)
--是下面公式的缩写
main = putStr "What is your name?”
	>> readLn
	>>= \a -> putStr "How old are you?”
	>> readLn
	>>= \b -> print (a,b) 

return and fail

instance Monad Maybe where
	(Just x) >>= k = k x
	Nothing >>= _ = Nothing
	
	(Just _) >> k = k
	Nothing >> _ = Nothing
	
	return = Just
	fail _ = Nothing

只是提醒一下:
1.return 和里面的 return 完全不同大多数其他语言。它不会结束函数执行或任何东西,它只需要一个正常值并将其放入语境
2.失败的类型(在 Monad 类中指定)是:fail :: String -> m a
该字符串旨在用于报告失败消息。
Maybe 类型只是忽略这一点并返回 Nothing on失败。但默认实现是:
fail s = error s

father :: Person -> Maybe Person
mother :: Person -> Maybe Person

两个函数,每个函数“查找”一个人并尝试返回这个
人的父亲/母亲;函数返回类型是 Maybe Person,因为我的研究可能还没弄清楚父母是谁

检查两个祖父是否已知

bothGrandfathers :: Person -> Maybe (Person, Person)
bothGrandfathers p
	= case father p of
		Nothing -> Nothing
		Just dad ->
			case father dad of
				Nothing -> Nothing
				Just gf1 -> -- found first grandfather
					case mother p of
						Nothing -> Nothing
						Just mum ->
							case father mum of
								Nothing -> Nothing
								Just gf2 -> -- found second grandfather
									Just (gf1, gf2) 

--或者
bothGrandfathers p
= father p >>=
	(\dad -> father dad >>=
		(\gf1 -> mother p >>=
			(\mum -> father mum >>=
				(\gf2 -> return (gf1,gf2) ))))
--或者
bothGrandfathers p = do {
	dad <- father p;
	gf1 <- father dad;
	mum <- mother p;
	gf2 <- father mum;
	return (gf1, gf2);
}

其他
• 标准的 Monad 类只需要这四个函数。
• 没有标准的用于提取价值的功能在Monad
• 您当然可以提供自己的。例如,Maybe 提供来自Just.
• 另一方面,IO 是单向 Monad。
• 因为你无法从 IO monad 中逃脱,所以不可能写一个在 IO monad 中进行计算但其结果类型为不包括 IO 类型构造函数。这意味着任何函数结果类型不包含 IO 类型构造函数保证不使用IO 单子。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值