Haskell 惰性评估和IO副作用混淆
此代码(取自): 很明显,他对我很感兴趣Haskell 惰性评估和IO副作用混淆,haskell,functional-programming,lazy-evaluation,side-effects,Haskell,Functional Programming,Lazy Evaluation,Side Effects,此代码(取自): 很明显,他对我很感兴趣 main = putStr "Hey, " >>= (\_ -> putStr "I'm " >>= (\_ -> putStrLn "Andy!")) 据我所知,这句话可以解释为“为了让安迪”安迪!我首先需要输入“我是”,为了做到这一点,我首先需要输入“嘿,” 我不同意这种解释,这很烦人,因为编译器显然没有这样做,让我感到困惑。我的问题是lambda忽略了它们的参数
main = putStr "Hey, " >>=
(\_ -> putStr "I'm " >>=
(\_ -> putStrLn "Andy!"))
据我所知,这句话可以解释为“为了让安迪”安迪!我首先需要输入“我是”,为了做到这一点,我首先需要输入“嘿,”
我不同意这种解释,这很烦人,因为编译器显然没有这样做,让我感到困惑。我的问题是lambda忽略了它们的参数,在惰性求值期间,这种事情不应该被识别和短路吗
当然,绑定也会返回一个IO操作,当该IO操作落入main时,它会被执行。但是如何阻止它打印“嘿,Andy!我是?,我怀疑是bind在做什么
另外,类型为“IO()”的IO操作如何携带足够的信息以允许运行时系统打印“嘿,我是安迪!“?IO()与打印“你好,世界!”!“还是写入文件
另一个例子是monad的维基百科页面:
加糖版本:
do
putStrLn "What is your name?"
name <- getLine
putStrLn ("Nice to meet you, " ++ name ++ "!")
putStrLn "What is your name?" >>=
(\_ ->
getLine >>=
(\name ->
putStrLn ("Nice to meet you, " ++ name ++ "!")))
这里也有类似的故事
我想我只需要看看bind for IO的定义,然后它就会清楚了。如果有人能帮助我逐步了解程序是如何评估的,并确定副作用发生的确切时刻,那将非常有帮助。阅读西蒙·佩顿·琼斯的“”论文
有关相关问题,请参阅
- ,
>=
可以被看作是一个列表构造函数:
data IO = [Primitive]
IO子系统解构main
的值并使用该列表。也就是说,`main只是一个列表。因此,您可能想看看上面的Haskell入口点的定义,
main,
bind`相当无趣
您还可以阅读关于haskell历史的论文,并查看IO子系统的早期版本,以了解正在发生的事情
还可以看看康纳尔·艾略特的讽刺文章
函数纯度的定义是非常重要的,我记得有一篇文章详细阐述了这个定义,但我不记得标题。我认为如果你把这些动作重新看作函数,这会更容易理解。你的绑定示例(
do{foo在一个真正的Haskell实现中查看IO
可能会比它所启发的更让人困惑。但是可以这样定义IO
(假设您了解GADT):
因此,当您评估一个程序(类型为IO()
)时,它所做的只是构建一个类型为IO()
的数据结构,该结构描述了一旦执行它,与世界的交互将如何发生。然后,您可以想象执行引擎是用C编写的,而所有影响都发生在那里
所以
与
main = Bind (PutStr "Hey, ") (\ _ -> Bind (PutStr "I'm ") (\ _ -> PutStr "Andy!"))
它们的顺序来自于执行引擎的工作方式
也就是说,我知道没有Haskell实现是这样做的。真正的实现倾向于将IO
实现为一个状态单子,带有一个表示正在传递的真实世界的标记(这是保证排序的原因),而像putStr
这样的原语只是对C函数的调用
我想我只需要看看IO绑定的定义,然后
一切都会好起来的
是的,你应该这样做。其实很简单,如果我没记错的话,事情会是这样的
newtype IO = IO (RealWorld -> (a, RealWorld))
(IO f) >>= g = ioBind f g
where
ioBind :: (RealWorld -> (a, RealWorld)) -> (a -> IO b) -> RealWorld -> (b, RealWorld)
ioBind f g rw = case f rw of
(a, rw@RealWorld) -> case g a of
IO b -> b rw
“诀窍”在于,每个IO值实际上基本上都是一个函数,但要对其求值,您需要一个类型为RealWorld
的标记。只有一个实例可以提供这样的值—运行main的运行时系统(当然,还有不能命名的函数)
我想我只需要看看IO
的bind
的定义,然后一切就都清楚了
IO
类型在单独的模块中声明:
-- ghc-8.6.5/libraries/ghc-prim/GHC/Types.hs; line 169
newtype IO a = IO (State# RealWorld -> (# State# RealWorld, a #))
Philip Wadler提供的IO
和bind
操作符的更多示例,包括标准ML的定义(第3.4节,第24页).有趣的是,每个人都会通过类比来解释>>=关于IO值。难道前奏曲中没有定义吗?为什么没有人引用它?是bind over IO魔术发生的地方吗?我现在正在阅读令人尴尬的队报,甚至连SPJ都不敢真正定义bind。IO
类型是抽象的,所以Haskell标准中没有对>=
的定义。根据您实现IO
的方式,您将有不同的>=
实现。如果深入研究ghc,您会发现IO
是一个状态单子,而>=
只是为状态单子绑定。(编译器内部有更多的魔法来提高效率。)@ThellonKnuckle它不是对“值”的类比。它是对另一个纯IO实现的类比,使用旧的main::[Request]->[Response]
idea。另外,最有趣的部分不是绑定,而是IO monad的runIO
。由于IO monad是抽象的,我们必须像SPJ那样提供抽象的解释,或者像我一样使用一些实现策略。+1对于GADT方法和“在真实的Haskell实现中查看IO”可能会让更多人困惑n它启发了‘东西’。“lambda忽略了它们的参数,在惰性求值期间,这种事情不应该被识别和短路吗?”当然!(>>=)
的第二个参数在这里是一个特别懒惰的函数,但是(>>=)
函数本身并不懒惰。
main = do putStr "Hey, "
putStr "I'm "
putStrLn "Andy!"
main = Bind (PutStr "Hey, ") (\ _ -> Bind (PutStr "I'm ") (\ _ -> PutStr "Andy!"))
newtype IO = IO (RealWorld -> (a, RealWorld))
(IO f) >>= g = ioBind f g
where
ioBind :: (RealWorld -> (a, RealWorld)) -> (a -> IO b) -> RealWorld -> (b, RealWorld)
ioBind f g rw = case f rw of
(a, rw@RealWorld) -> case g a of
IO b -> b rw
-- ghc-8.6.5/libraries/base/GHC/Base.hs; line 1381
bindIO :: IO a -> (a -> IO b) -> IO b
bindIO (IO m) k = IO (\ s -> case m s of (# new_s, a #) -> unIO (k a) new_s)
-- ghc-8.6.5/libraries/base/GHC/Base.hs; line 1387
unIO :: IO a -> (State# RealWorld -> (# State# RealWorld, a #))
unIO (IO a) = a
-- ghc-8.6.5/libraries/ghc-prim/GHC/Types.hs; line 169
newtype IO a = IO (State# RealWorld -> (# State# RealWorld, a #))