从IO获得价值而不是计算本身 [英] Get value from IO rather than the computation itself

查看:125
本文介绍了从IO获得价值而不是计算本身的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

作为Haskell的新手,我正在努力通过编写一个简单的命令式玩具语言的解释器来提高我的技能。



这个表达式之一语言是 input ,它从标准输入中读取一个整数。然而,当我将这个表达式的值赋给一个变量,然后再使用这个变量时,我看起来并不是我实际上存储了读取一个值的计算,而是读取值本身。这意味着例如声明

  x =输入; 
y = x + x;

将导致解释器调用输入过程三次,而不是一次。

在评估模块内部,我使用 Map 来存储变量的值。因为我需要处理IO,所以它会被封装在一个 IO monad中,如以下最小示例中的永久化:

 输入限定的Data.Map作为地图

type State = Map.Map String Int
type Op = Int - > Int - > Int

input :: String - > IO状态 - > IO State
input x state = do line < - getLine
st < - state
return $ Map.insert x(读行)st

get: :字符串 - > IO状态 - > IO Int
get x state = do st < - state
return $ case Map.lookup x st of
Just i - > i

eval :: String - > Op - >字符串 - > IO状态 - > IO Int
eval l op r state = do i< - get l state
j< - get r state
return $ op ij

main :: IO ()
main = do let state = return Map.empty
let state'= inputxstate
val < - evalx(+)xstate'
putStrLn。 show $ val

main 函数模拟 x 的赋值,而第三行模拟对二进制 + 运算符的求值。



我的问题是:我如何解决这个问题,以便上面的代码只输入一次?我怀疑它是导致问题的 IO - 包装,但是因为我们正在处理IO,所以我没有办法解决这个问题..?

$请记住, IO状态不是一个实际的状态,而是一个 code> IO
最终产生 State 的机器。让我们考虑 input 作为 IO - 机器转换器

  input :: String  - > IO状态 - > IO State 
input x state = do line < - getLine
st < - state
return $ Map.insert x(read line)st

在这里,提供了一个生成状态的机器,我们创建一个更大的机器,它接受传递的状态并添加 read 来自输入行。同样,要清楚,输入名称st IO -machine,它是对<$ c现在我们来看看<$ c $> IO -machine st p

c> get

  get :: String  - > IO状态 - > IO Int 
get x state = do st < - state
return $ case Map.lookup x st of
Just i - > i

这里我们有另一个 IO --machine变压器。给定一个名称和一个产生 State 的< IO -machine, get 将产生一个 IO - 机器,它返回一个数字。再次注意, get name st 被固定为始终使用由(固定的,输入) IO --machine st



我们将这些部分合并到 eval / p>

  eval :: String  - > Op  - >字符串 - > IO状态 - > IO Int 
eval l op r state = do i< - get l state
j < - get r state
return $ op ij

这里我们称之为 get l get r 每个都在相同的 IO -machine state 上,从而产生两个(完全独立的) IO -machines get l state get r state 。然后我们一个接一个评估它们的 IO 效果并返回结果的 op - 组合。



让我们来看看 main 中内置的 IO -machines。在第一行中,我们生成了一个 ,,, code> return Map.empty 。这个 IO -machine,每次运行时都不会产生副作用,以返回 fresh ,空白 Map。 Map



在第二行中,我们产生一种新的 IO - 称为状态'的机器。这 IO -machine是基于状态 IO 机器,但它也要求输入一行。因此,要清楚的是,每次运行 state ,都会生成一个新的 Map.Map ,然后输入一行读取读取x。



应该清楚这是怎么回事,但现在当我们检查第三行时,我们发现我们传递了 state' IO -machine,转换为 eval 。以前我们声明 eval 会对其输入 IO --machine运行两次,每个名称一次,然后合并结果。在这一点上,应该清楚发生了什么。



总而言之,我们构建了一种机器,它绘制输入并将其作为整数读取,并将其分配给一个名称在一个空白 Map.Map 中。然后,我们将这个 IO -machine构建成一个较大的一个,它在两个单独的调用中使用第一个 IO --machine两次,以收集数据并将其与 Op 结合。

最后,我们运行这个 eval 使用 do 符号的机器((< - )箭头表示正在运行机器)。很明显,它应该收集两条独立的线。






那么我们真的想做什么?那么,我们需要模拟 IO monad中的环境状态,而不是传递 Map.Map s。这很容易通过使用 IORef

 导入数据。 IORef 

input :: IORef State - >字符串 - > IO()
input ref name = do
line< - getLine
modifyIORef ref(Map.insert name(read line))

eval :: IORef状态 - > Op - >字符串 - >字符串 - > IO Int
eval ref op lr = do
stateSnapshot< - readIORef ref
let i = Map.lookup l stateSnapshot
Just j = Map.lookup l stateSnapshot
返回(op ij)

main = do
st < - newIORef Map.empty - 创建一个空白状态,嵌入到IO中,不是值
输入st x - 请求输入*一次*
val < - eval st(+)xx - 计算op
putStrLn。显示$ val


Being quite new to Haskell, I'm currently trying to improve my skills by writing an interpreter for a simple imperative toy language.

One of the expressions in this language is input, which reads a single integer from standard input. However, when I assign the value of this expression to a variable and then use this variable later, it seems ot me that I actually stored the computation of reading a value rather the read value itself. This means that e.g. the statements

x = input;
y = x + x;

will cause the interpreter to invoke the input procedure three times rather than one.

Internally in the evaluator module, I use a Map to store the values of variables. Because I need to deal with IO, this gets wrapped in an IO monad, as immortalized in the following minimal example:

import qualified Data.Map as Map

type State = Map.Map String Int
type Op = Int -> Int -> Int

input :: String -> IO State -> IO State
input x state = do line <- getLine
                   st <- state
                   return $ Map.insert x (read line) st

get :: String -> IO State -> IO Int
get x state = do st <- state
                 return $ case Map.lookup x st of
                            Just i -> i

eval :: String -> Op -> String -> IO State -> IO Int
eval l op r state = do i <- get l state
                       j <- get r state
                       return $ op i j

main :: IO ()
main = do let state = return Map.empty
          let state' = input "x" state
          val <- eval "x" (+) "x" state'
          putStrLn . show $ val

The second line in the main function simulates the assignment of x, while the third line simulates the evaluation of the binary + operator.

My question is: How do I get around this, such that the code above only inputs once? I suspect that it is the IO-wrapping that causes the problem, but as we're dealing with IO I see no way out of that..?

解决方案

Remember that IO State is not an actual state, but instead the specification for an IO machine which eventually produces a State. Let's consider input as an IO-machine transformer

input :: String -> IO State -> IO State
input x state = do line <- getLine
                   st <- state
                   return $ Map.insert x (read line) st

Here, provided a machine for producing a state, we create a bigger machine which takes that passed state and adding a read from an input line. Again, to be clear, input name st is an IO-machine which is a slight modification of the IO-machine st.

Let's now examine get

get :: String -> IO State -> IO Int
get x state = do st <- state
                 return $ case Map.lookup x st of
                            Just i -> i

Here we have another IO-machine transformer. Given a name and an IO-machine which produces a State, get will produce an IO-machine which returns a number. Note again that get name st is fixed to always use the state produced by the (fixed, input) IO-machine st.

Let's combine these pieces in eval

eval :: String -> Op -> String -> IO State -> IO Int
eval l op r state = do i <- get l state
                       j <- get r state
                       return $ op i j

Here we call get l and get r each on the same IO-machine state and thus produce two (completely independent) IO-machines get l state and get r state. We then evaluate their IO effects one after another and return the op-combination of their results.

Let's examine the kinds of IO-machines built in main. In the first line we produce a trivial IO-machine, called state, written return Map.empty. This IO-machine, each time it's run, performs no side effects in order to return a fresh, blank Map.Map.

In the second line, we produce a new kind of IO-machine called state'. This IO-machine is based off of the state IO-machine, but it also requests an input line. Thus, to be clear, each time state' runs, a fresh Map.Map is generated and then an input line is read to read some Int, stored at "x".

It should be clear where this is going, but now when we examine the third line we see that we pass state', the IO-machine, into eval. Previously we stated that eval runs its input IO-machine twice, once for each name, and then combines the results. By this point it should be clear what's happening.

All together, we build a certain kind of machine which draws input and reads it as an integer, assigning it to a name in a blank Map.Map. We then build this IO-machine into a larger one which uses the first IO-machine twice, in two separate invocations, in order to collect data and combine it with an Op.

Finally, we run this eval machine using do notation (the (<-) arrow indicates running the machine). Clearly it should collect two separate lines.


So what do we really want to do? Well, we need to simulate ambient state in the IO monad, not just pass around Map.Maps. This is easy to do by using an IORef.

import Data.IORef

input :: IORef State -> String -> IO ()
input ref name = do
  line <- getLine
  modifyIORef ref (Map.insert name (read line))

eval :: IORef State -> Op -> String -> String -> IO Int
eval ref op l r = do
  stateSnapshot <- readIORef ref
  let Just i = Map.lookup l stateSnapshot
      Just j = Map.lookup l stateSnapshot
  return (op i j)

main = do
  st <- newIORef Map.empty   -- create a blank state, embedded into IO, not a value
  input st "x"               -- request input *once*
  val <- eval st (+) "x" "x" -- compute the op
  putStrLn . show $ val

这篇关于从IO获得价值而不是计算本身的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆