我认为原则上haskell的类型系统会禁止从纯粹的函数调用不纯的函数(即f :: a -> IO b
),但今天我意识到通过用return
调用它们它们编译得很好。例如:
h :: Maybe ()
h = do
return $ putStrLn "???"
return ()
现在,h
可能在monad中工作,但它仍然是一个纯函数。编译和运行它只是按照预期返回Just ()
,而不实际执行任何I / O.我认为haskell的懒惰将事物放在一起(即putStrLn
的返回值没有被使用 - 并且因为它的值构造函数被隐藏而且我无法模式匹配它,所以不能,但为什么这个代码是合法的?还有其他原因可以让这个允许吗?
作为奖励,相关问题:一般来说,是否有可能禁止monad的行为从其他行为中执行?怎么样?
答案 0 :(得分:19)
IO动作与其他动作一样是一流的值;这就是让Haskell的IO如此富有表现力的原因,允许您从头开始构建更高阶的控制结构(如mapM_
)。懒惰与此无关, 1 只是你实际上并没有执行动作。你只需要构造值Just (putStrLn "???")
,然后扔掉它。
putStrLn "???"
现有不会导致在屏幕上打印一行。就其本身而言,putStrLn "???"
只是某些IO的描述,可以将行打印到屏幕上。发生的唯一执行是执行main
,它是从其他IO操作构建的,或者您在GHCi中键入的任何操作。有关详细信息,请参阅introduction to IO。
事实上,完全可以想象你可能想要在IO
内处理Maybe
行动;想象一个函数String -> Maybe (IO ())
,它检查字符串的有效性,如果它有效,则返回一个IO动作来打印从字符串派生的一些信息。这可能正是因为Haskell的一流IO操作。
但是monad没有能力执行另一个monad的动作,除非你赋予它那个能力。
1 实际上,h = putStrLn "???" `seq` return ()
也不会导致执行任何IO,即使它强制评估putStrLn "???"
。
答案 1 :(得分:4)
让我们去喝酒!
h = do return (putStrLn "???"); return ()
-- rewrite (do foo; bar) as (foo >> do bar)
h = return (putStrLn "???") >> do return ()
-- redundant do
h = return (putStrLn "???") >> return ()
-- return for Maybe = Just
h = Just (putStrLn "???") >> Just ()
-- replace (foo >> bar) with its definition, (foo >>= (\_ -> bar))
h = Just (putStrLn "???") >>= (\_ -> Just ())
现在,当您评估h
时会发生什么?*嗯,对于Maybe,
(Just x) >>= f = f x
Nothing >>= f = Nothing
所以我们模式匹配第一个案例
f x
-- x = (putStrLn "???"), f = (\_ -> Just ())
(\_ -> Just ()) (putStrLn "???")
-- apply the argument and ignore it
Just ()
请注意我们为了评估此表达式而不必执行putStrLn "???"
。
* n.b。有点不清楚“脱毒”停止和“评估”开始。这取决于编译器的内联决策。纯计算可以在编译时完全评估。