Haskell中的I / O是否具有功能性?

时间:2011-06-19 00:12:17

标签: haskell functional-programming io

刚刚开始来看看Haskell(我以前的FP体验是在Scheme中),而我came across this code

do { putStrLn "ABCDE" ; putStrLn "12345" }

对我来说,这是程序式编程,如果有的话 - 特别是因为副作用的连续性。

有人请说明这段代码在任何方面都是“功能性的”吗?

6 个答案:

答案 0 :(得分:20)

虽然它似乎是一个程序性程序,但上述语法被翻译成功能程序,如下所示:

   do { putStrLn "ABCDE" ; putStrLn "12345" }
=>
   IO (\ s -> case (putStrLn "ABCDE" s) of
                  ( new_s, _ ) -> case (putStrLn "12345" new_s) of
                                      ( new_new_s, _) -> ((), new_new_s))

也就是说,一系列具有唯一世界参数的嵌套函数穿过它们,对“原始函数”的调用进行“程序性”排序。此设计支持将命令式编程编码为函数式语言。

对此设计所依据的语义决策的最佳介绍是"The Awkward Squad"论文,

enter image description here

答案 1 :(得分:13)

我认为我们不能清楚地回答这个问题,因为“功能性”是一个模糊的概念,并且它的含义存在矛盾的想法。所以我更喜欢彼得兰丁的建议替代术语“外延”,这是精确和实质性的,对我来说,心脏和心灵。函数式编程的灵魂以及它对于等式推理的好处。有关Landin定义的一些提示,请参阅these commentsIO 不是指示性的。

答案 2 :(得分:5)

以这种方式思考。它实际上并不“执行”IO指令。 IO monad是一个纯值,它封装了要执行的“命令式计算”(但实际上并没有执行它)。您可以使用monad运算符和类似“do”的结构以纯粹的方式将monad(计算)组合成更大的“计算”。尽管如此,本身并没有“执行”。事实上,在某种程度上,Haskell程序的整个目的是将一个大的“计算”组合在一起,即main值(类型为IO a)。当你运行程序时,就是运行这个“计算”。

答案 3 :(得分:3)

这是monad。阅读do-notation,了解封面背后的内容。

答案 4 :(得分:2)

请有人说明这段代码

do { putStrLn "ABCDE" ; putStrLn "12345" }

在任何方面都具有“功能性”吗?

这就是我如何看待Haskell I / O的当前情况;通常的免责声明适用于> _ << / p>

如果用“功能性”来表示“完全功能性”,则即刻(2020年6月),这取决于您的Haskell 实现。但这并非总是如此-实际上,Haskell 语言的原始I / O模型真的完全是功能!

在Philip Wadler的How to Declare an Imperative的帮助下,可以回到哈斯克尔的早期旅行了:

import Prelude hiding (IO)
import qualified Prelude (IO)

import Control.Concurrent.Chan(newChan, getChanContents, writeChan) 
import Control.Monad((<=<))


 -- pared-back emulation of retro-Haskell I/O
 --
runDialogue :: Dialogue -> Prelude.IO ()
runDialogue d =
  do ch <- newChan
     l <- getChanContents ch
     mapM_ (writeChan ch <=< respond) (d l)

respond :: Request -> Prelude.IO Response
respond Getq     = fmap Getp getChar
respond (Putq c) = putChar c >> return Putp

main = runDialogue (retro_main :: Dialogue)

{-
          implementation side
  -----------------------------------
  ========== retro-Haskell ==========
  -----------------------------------
             language side
-}

 -- pared-back definitions for retro-Haskell I/O
 -- from page 14 of Wadler's paper
 --
data Request = Getq | Putq Char
data Response = Getp Char | Putp

type Dialogue = [Response] -> [Request]

(将其扩展到retro-Haskell的所有I / O上都是非常热衷的读者的一种练习;-)

您去了:纯功能性I / O!响应将流传输到 main retro_main,然后将请求流传输回:

retro-Haskell program interacting with its surroundings

拥有如此优雅的纯度,您可以高兴地定义:

 -- from page 15 of Wadler's paper
echoD :: Dialogue
echoD p =
  Getq :
    case p of
      Getp c : p' ->
        if (c == '\n') then
          []
        else
          Putq c :
            case p' of
              Putp : p'' -> echoD p''

您看起来很困惑-没关系;你会明白的:-D

这是A History of Haskell第24页的更复杂的示例:

{-

main ~(Success : ~((Str userInput) : ~(Success : ~(r4 : _))))
  = [ AppendChan stdout "enter filename\n",
      ReadChan stdin,
      AppendChan stdout name,
      ReadFile name,
      AppendChan stdout
          (case r4 of
              Str contents -> contents
              Failure ioerr -> "can't open file")
    ] where (name : _) = lines userInput

-}

你还在吗?

是您旁边的垃圾桶吗? ??你病了吗该死的。

那么-也许您会发现,使用易于识别的界面会容易一些:

 -- from page 12 of Wadler's paper
 --
echo  :: IO ()
echo  =  getc >>= \ c ->
         if (c == '\n') then
           done
         else
           putc c >>
           echo


 -- from pages 3 and 7
 --
puts  :: String -> IO ()
puts []    = done
puts (c:s) = putc c >> puts s

done :: IO ()
done = return ()


 -- based on pages 16-17
 --
newtype IO a = MkIO { enact :: Reality -> (Reality, a) }
type Reality = ([Response], [Request])

bindIO    :: IO a -> (a -> IO b) -> IO b
bindIO m k =  MkIO $ \ (p0, q2) -> let ((p1, q0), x) = enact m     (p0, q1)
                                       ((p2, q1), y) = enact (k x) (p1, q2)
                                   in
                                       ((p2, q0), y)


unitIO :: a -> IO a
unitIO x = MkIO $ \ w -> (w, x)

putc :: Char -> IO ()
putc c  = MkIO $ \ (p0, q1) -> let q0        = Putq c : q1
                                   Putp : p1 = p0
                               in
                                   ((p1, q0), ())

getc :: IO Char
getc    = MkIO $ \ (p0, q1) -> let q0          = Getq : q1
                                   Getp c : p1 = p0
                               in
                                   ((p1, q0), c)

mainD :: IO a -> Dialogue
mainD main = \ p0 -> let ((p1, q0), x) = enact main (p0, q1)

                         q1            = []
                     in
                         q0

 -- making it work
instance Monad IO where
    return = unitIO
    (>>=)  = bindIO

我还提供了您的示例代码;也许会有所帮助:

 -- local version of putStrLn
putsl :: String -> IO ()
putsl s = puts s >> putc '\n'

 -- bringing it all together
retro_main :: Dialogue
retro_main = mainD $ do { putsl "ABCDE" ; putsl "12345" }

是的:这仍然是纯功能性的I / O;检查retro_main的类型。

显然,基于对话的I / O最终在空间站中像臭鼬一样受欢迎。将其填充到单子界面中只会将恶臭(及其来源)限制在该站的一小部分内-那时,Haskellers希望这个小臭鼬消失!

因此,Haskell中用于I / O的抽象monadic接口已成为标准-小部分及其辛辣的乘员从空间站分离出来并被拖回地球,那里的新鲜空气更多 丰富。空间站上的气氛有所改善,大多数Haskellers继续做其他事情。

但是有些人对此新的I / O抽象模型感到na恼-was Haskell still purely functional?

如果模型是基于抽象的,那么在这种情况下:

  • 一种抽象的I / O操作:IO
  • 用于构造简单I / O操作的抽象函数:return
  • 用于组合I / O操作的抽象函数:(>>=)catch
  • 特定I / O操作的抽象功能:getArgsgetEnv

然后这些实体的实际定义将特定于Haskell的每种实现。

代替询问:

  • Haskell中的I / O纯粹是功能正常的吗?

现在应该问的是:

  • Haskell的实现中的I / O是否完全起作用?

所以您的问题的答案:


请有人说明这段代码

do { putStrLn "ABCDE" ; putStrLn "12345" }

在任何方面都具有“功能性”吗?


现在取决于您使用的Haskell的实现方式。

不是您想要的答案?现在是the skunk ...

答案 5 :(得分:0)

这不是功能代码。为什么会这样?