纯函数中IO动作的含义是什么?

时间:2012-03-22 09:35:47

标签: haskell monads

我认为原则上haskell的类型系统会禁止从纯粹的函数调用不纯的函数(即f :: a -> IO b),但今天我意识到通过用return调用它们它们编译得很好。例如:

h :: Maybe ()
h = do
    return $ putStrLn "???"
    return ()

现在,h可能在monad中工作,但它仍然是一个纯函数。编译和运行它只是按照预期返回Just (),而不实际执行任何I / O.我认为haskell的懒惰将事物放在一起(即putStrLn的返回值没有被使用 - 并且因为它的值构造函数被隐藏而且我无法模式匹配它,所以不能,但为什么这个代码是合法的?还有其他原因可以让这个允许吗?

作为奖励,相关问题:一般来说,是否有可能禁止monad的行为从其他行为中执行?怎么样?

2 个答案:

答案 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。有点不清楚“脱毒”停止和“评估”开始。这取决于编译器的内联决策。纯计算可以在编译时完全评估。