简体   繁体   English

Haskell中的IO类型是什么

[英]What is the IO type in Haskell

I am new to the Haskell programming language, I keep on stumbling on the IO type either as a function parameter or a return type.我是 Haskell 编程语言的新手,我一直在IO类型上绊倒,无论是作为 function 参数还是返回类型。

playGame :: Screen -> IO ()

OR或者

gameRunner :: IO String -> (String -> IO ()) -> Screen -> IO ()

How does this work, I am a bit confused because I know a String expects words and an Int expects numbers.这是如何工作的,我有点困惑,因为我知道 String 需要单词而 Int 需要数字。 Whats does the IO used in functions expect or Return?函数期望或返回中使用的IO是什么?

IO is the way how Haskell differentiates between code that is referentially transparent and code that is not. IO是 Haskell 如何区分引用透明代码和不透明代码的方式。 IO a is the type of an IO action that returns an a . IO a是返回a的 IO 操作的类型。

You can think of an IO action as a piece of code with some effect on the real world that waits to get executed.您可以将 IO 操作视为一段对等待执行的现实世界有一定影响的代码。 Because of this side effect, an IO action is not referentially transparent;由于这种副作用,IO 操作不是引用透明的; therefore, execution order matters.因此,执行顺序很重要。 It is the task of the main function of a Haskell program to properly sequence and execute all IO actions.正确排序和执行所有 IO 动作是 Haskell 程序的main function 的任务。 Thus, when you write a function that returns IO a , what you are actually doing is writing a function that returns an action that eventually - when executed by main - performs the action and returns an a .因此,当您编写返回IO a的 function 时,您实际上正在做的是编写 function 执行最终返回的动作 - 当由main执行并返回a动作时。

Some more explanation:更多解释:

Referential transparency means that you can replace a function by its value.引用透明性意味着您可以将 function 替换为其值。 A referentially transparent function cannot have any side effects;一个参照透明的 function 不会有任何副作用; in particular, a referentially transparent function cannot access any hardware resources like files, network, or keyboard, because the function value would depend on something else than its parameters.特别是,引用透明的 function 无法访问任何硬件资源,如文件、网络或键盘,因为 function 值将取决于其参数以外的其他内容。

Referentially transparent functions in a functional language like Haskell are like math functions (mappings between domain and codomain), much more than a sequence of imperative instructions on how to compute the function's value.像 Haskell 这样的函数式语言中的引用透明函数就像数学函数(域和余域之间的映射),而不仅仅是关于如何计算函数值的命令式指令序列。 Therefore, Haskell code says the compiler that a function is applied to its arguments, but it does not say that a function is called and thus actually computed. Therefore, Haskell code says the compiler that a function is applied to its arguments, but it does not say that a function is called and thus actually computed.

Therefore, referentially transparent functions do not imply the order of execution.因此,引用透明函数并不意味着执行顺序。 The Haskell compiler is free to evaluate functions in any way it sees fit - or not evaluate them at all if it is not necessary (called lazy evaluation). Haskell 编译器可以自由地以它认为合适的任何方式评估函数 - 或者如果没有必要则根本不评估它们(称为惰性评估)。 The only ordering arises from data dependencies , when one function requires the output of another function as input.唯一的排序来自数据依赖性,当一个 function 需要另一个 function 的 output 作为输入时。

Real-world side effects are not referentially transparent.现实世界的副作用不是引用透明的。 You can think of the real world as some sort of implicit global state that effectual functions mutate.您可以将现实世界视为某种隐含的全局 state ,有效函数会发生变异。 Because of this state, the order of execution matters: It makes a difference if you first read from a database and then update it, or vice versa.由于这个 state,执行顺序很重要:如果您首先从数据库中读取然后更新它会有所不同,反之亦然。

Haskell is a pure functional language, all its functions are referentially transparent and compilation rests on this guarantee. Haskell 是一种纯函数式语言,它的所有函数都是引用透明的,编译依赖于此保证。 How, then, can we deal with effectful functions that manipulate some global real-world state and that need to be executed in a certain order?那么,我们如何处理有效的函数,这些函数操纵一些全局真实世界 state 并且需要按特定顺序执行? By introducing data dependency between those functions.通过在这些函数之间引入数据依赖性。

This is exactly what IO does: Under the hood, the IO type wraps an effectful function together with a dummy state paramter. This is exactly what IO does: Under the hood, the IO type wraps an effectful function together with a dummy state paramter. Each IO action takes this dummy state as input and provides it as output.每个 IO 操作都将此虚拟 state 作为输入,并将其作为 output 提供。 Passing this dummy state parameter from one IO action to the next creates a data dependency and thus tells the Haskell compiler how to properly sequence all the IO actions. Passing this dummy state parameter from one IO action to the next creates a data dependency and thus tells the Haskell compiler how to properly sequence all the IO actions.

You don't see the dummy state parameter because it is hidden behind some syntactic sugar: the do notation in main and other IO actions, and inside the IO type.您看不到虚拟 state 参数,因为它隐藏在一些语法糖后面: main和其他 IO 操作中的do表示法,以及IO类型内部。

Briefly put:简单地说:

f1 :: A -> B -> C

is a function which takes two arguments of type A and B and returns a C .是一个 function ,它采用AB类型的两个 arguments 并返回一个C It does not perform any IO.它不执行任何 IO。

f2 :: A -> B -> IO C

is similar to f1 , but can also perform IO.f1类似,但也可以执行 IO。

f3 :: (A -> B) -> IO C

takes as an argument a function A -> B (which does not perform IO) and produces a C , possibly performing IO.将 function A -> B (不执行 IO)作为参数并生成C ,可能执行 IO。

f4 :: (A -> IO B) -> IO C

takes as an argument a function A -> IO B (which can perform IO) and produces a C , possibly performing IO. takes as an argument a function A -> IO B (which can perform IO) and produces a C , possibly performing IO.

f5 :: A -> IO B -> IO C

takes as an argument a value of type A , an IO action of type IO B , and returns a value of type C , possibly performing IO (eg by running the IO action argument one or more times). takes as an argument a value of type A , an IO action of type IO B , and returns a value of type C , possibly performing IO (eg by running the IO action argument one or more times).

Example:例子:

f6 :: IO Int -> IO Int
f6 action = do
   x1 <- action
   x2 <- action
   putStrLn "hello!"
   x3 <- action
   return (x1+x2+x3)

When a function returns IO () , it returns no useful value, but can perform IO.当 function 返回IO ()时,它没有返回有用的值,但可以执行 IO。 Similar to, say, returning void in C or Java.类似于在 C 或 Java 中返回void Your您的

gameRunner :: IO String -> (String -> IO ()) -> Screen -> IO ()

function can be called with the following arguments: function 可以用下面的 arguments 调用:

arg1 :: IO String
arg1 = do
   putStrLn "hello"
   s <- readLine
   return ("here: " ++ s)

arg2 :: String -> IO ()
arg2 str = do
   putStrLn "hello"
   putStrLn str
   putStrLn "hello again"

arg3 :: Screen
arg3 = ... -- I don't know what's a Screen in your context

Let's try answering some simpler questions first:让我们先尝试回答一些简单的问题:

  • What is the Maybe type in Haskell? Haskell 中的Maybe类型是什么?

    From chapter 21 (page 205) of the Haskell 2010 Report :来自Haskell 2010 报告的第 21 章(第 205 页):

     data Maybe a = Nothing | Just a

    it's a simple partial type - you have a value (conveyed via Just ) or you don't ( Nothing ).这是一个简单的部分类型 - 你有一个值(通过Just传达)或者你没有( Nothing )。

  • How does this work?这是如何运作的?

    Let's look at one possible Monad instance for Maybe :让我们看一下Maybe的一个可能的Monad实例:

     instance Monad Maybe where return = Just Just x >>= k = kx Nothing >>= _ = Nothing

    This monadic interface simplifies the use of values based on Maybe constructors eg instead of:这个 monadic 接口简化了基于Maybe构造函数的值的使用,例如,而不是:

     \f ox oy -> case ox of Nothing -> Nothing Just x -> case oy of Nothing -> Nothing Just y -> Just (fxy)

    you can simply write this:你可以简单地写这个:

     \f ox oy -> ox >>= \x -> oy >>= \y -> return (fxy)

    The monadic interface is widely applicable: from parsing to encapsulated state, and so much more. monadic 接口应用广泛:从解析到封装 state 等等。

  • What does the Maybe type used in functions expect or return?函数中使用的Maybe类型期望或返回什么?

    For a function expecting a Maybe -based value eg:对于 function 期望基于Maybe的值,例如:

     maybe:: b -> (a -> b) -> Maybe a -> b maybe _ f (Just x) = fx maybe d _ Nothing = d

    if its contents are being used in the function, then the function may have to deal with not receiving a value it can use ie Nothing .如果它的内容在 function 中使用,那么 function 可能不得不处理没有接收到它可以使用的值,即Nothing

    For a function returning a Maybe -based value eg:对于 function 返回基于Maybe的值,例如:

     invert:: Double -> Maybe Double invert 0.0 = Nothing invert d = Just (1/d)

    it just needs to use the appropriate constructors.它只需要使用适当的构造函数。

    One last point: observe how Maybe -based values are used - from starting simply (eg invert 0.5 or Just "here" ) to then define other, possibly more-elaborate Maybe -based values (with (>>=) , (>>) , etc) to ultimately be examined directly by pattern-matching, or abstractly by a suitable definition ( maybe , fromJust et al).最后一点:观察如何使用基于Maybe的值 - 从简单开始(例如invert 0.5Just "here" )然后定义其他可能更精细的基于Maybe的值(使用(>>=)(>>)等)最终通过模式匹配直接检查,或通过适当的定义抽象地检查( maybefromJust等人)。


Time for the original questions:原始问题的时间:

  • What is the IO type in Haskell? Haskell 中的IO类型是什么?

    From section 6.1.7 (page 75) of the Report:来自报告的第 6.1.7 节(第 75 页):

    The IO type serves as a tag for operations (actions) that interact with the outside world. IO类型用作与外界交互的操作(动作)的标记。 The IO type is abstract: no constructors are visible to the user. IO类型是抽象的:没有构造函数对用户可见。 IO is an instance of the Monad and Functor classes. IOMonadFunctor类的一个实例。

    the crucial point being:关键是:

    The IO type is abstract: no constructors are visible to the user. IO类型是抽象的:没有构造函数对用户可见。

    No constructors?没有构造函数? That begs the next question:这就引出了下一个问题:

  • How does this work?这是如何运作的?

    This is where the versatility of the monadic interface steps in: the flexibility of its two key operatives - return and (>>=) in Haskell - substantially make up for IO -based values being abstract.这就是单子接口的多功能性所在:它的两个关键操作符的灵活性——Haskell 中的return(>>=) ——基本上弥补了基于IO的抽象值。

    Remember that observation about how Maybe -based values are used?还记得关于如何使用基于Maybe的值的观察吗? Well, IO -based values are used in similar fashion - starting simply (eg return 1 , getChar or putStrLn "Hello, there!" ) to defining other IO -based values (with (>>=) , (>>) , catch , etc) to ultimately form Main.main .那么,基于IO的值以类似的方式使用 - 简单地开始(例如return 1getCharputStrLn "Hello, there!" )定义其他基于IO的值(使用(>>=)(>>)catch等)最终形成Main.main

    But instead of pattern-matching or calling another function to extract its contents, Main.main is processed directly by the Haskell implementation.但不是模式匹配或调用另一个 function 来提取其内容, Main.main直接由 Haskell 实现处理。

  • What does the IO used in functions expect or return?函数中使用的IO期望或返回什么?

    For a function expecting a IO -based value eg:对于 function 期望基于IO的值,例如:

     echo:: IO () echo:: getChar >>= \c -> if c == '\n' then return () else putChar c >> echo

    if its contents are being used in the function, then the function usually returns an IO -based value.如果它的内容在 function 中使用,那么 function 通常会返回基于IO的值。

    For a function returning a IO -based value eg:对于 function 返回基于IO的值,例如:

     newLine:: IO () newLine = putChar '\n'

    it just needs to use the appropriate definitions.它只需要使用适当的定义。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM