【问题标题】:Perform IO action, but return other type执行 IO 动作,但返回其他类型
【发布时间】:2016-05-30 18:55:05
【问题描述】:

对于一个项目,我们的任务是编写一种命令式语言并通过 Haskell 执行它。解析器(此处省略)和部分评估已完成。现在只剩下编码效果了。操纵一个小机器人。

给定以下代码:

data Env = Env [Binding]
instance Show Env where
  show (Env (x:xs)) = show x ++ ", " ++ show (Env xs)
  show (Env []) = ""

data Binding = Binding (String,Int)
instance Show Binding where
  show (Binding x) = fst x ++ " : " ++ show (snd x)

lookup' :: String -> Env -> Int
lookup' zoek (Env env) = case elemIndex zoek [fst x | Binding x <- env] of
  Just y ->  y
  Nothing -> error "Not found"


eval :: Stmt -> Env ->  Env
eval (Seq s) env = foldl (flip eval) env s
eval (Assign varName aexpr) env = evalAssign varName aexpr env
eval (If bool stmt1 stmt2) env = evalIf bool stmt1 stmt2 env
eval (While bool stmt) env = undefined
eval (MotorInstruct string aExpr) env = undefined
eval (SensorRead string) env = undefined
eval Skip env = env

evalAExpr :: AExpr -> Env -> Int
evalAExpr (IntConst int) _  = fromInteger int
evalAExpr (Neg a) env = - evalAExpr a env
evalAExpr (ABinary Add a b) env = evalAExpr a env + evalAExpr b env
evalAExpr (ABinary Subtract a b) env = evalAExpr a env - evalAExpr b env
evalAExpr (ABinary Multiply a b) env = evalAExpr a env * evalAExpr b env
evalAExpr (ABinary Divide a b) env = evalAExpr a env `div` evalAExpr b env
evalAExpr (Var x) env = getElementAtEnv env (lookup' x env)
  where
    getElementAtEnv (Env env) index = getSndFromBinding (env !! index)
    getSndFromBinding (Binding (_,t)) = t


evalBExpr :: BExpr -> Env -> Bool
evalBExpr (BoolConst bool) _ = bool
evalBExpr (Not expr) env = not $ evalBExpr expr env
-- Boolean operators
evalBExpr (BBinary And a b) env = evalBExpr a env && evalBExpr b env
evalBExpr (BBinary Or a b) env = evalBExpr a env || evalBExpr b env
-- Relational operators
evalBExpr (RBinary Greater a b) env = evalAExpr a env > evalAExpr b env
evalBExpr (RBinary Less a b) env = evalAExpr a env < evalAExpr b env
evalBExpr (RBinary Equal a b) env = evalAExpr a env == evalAExpr b env


evalIf ::  BExpr -> Stmt -> Stmt -> Env -> Env
evalIf expr s1 s2 env = if evalBExpr expr env
  then
    eval s1 env
  else
    eval s2 env


evalAssign :: String -> AExpr -> Env -> Env
evalAssign term s (Env env)= if term `elem` transform
  then
    Env ( take (lookup' term (Env env)) env ++ [Binding (term, evalAExpr s (Env env))]++ drop (lookup' term (Env env) + 1) env)
  else
    Env (env ++ [Binding (term, evalAExpr s (Env env))])
  where transform = [ fst ele | Binding ele <- env]


zoekMotor :: String -> Int
zoekMotor "left" = 0x9
zoekMotor "right" = 0xa
zoekMotor _ = error "No such motor"


sendToMotor :: String -> Int -> IO()
sendToMotor m s = do
  bot <- openMBot
  sendCommand bot $ setMotor (zoekMotor m) s s
  closeMBot bot

evalMotorInstruct :: String -> AExpr -> Env -> Env
evalMotorInstruct welke waarde env = do
  sendToMotor welke (evalAExpr waarde env)
  return env

如何在我的评估函数evalMotorInstruct 中执行函数sendToMotor(返回IO()),它应该返回Env?我对如何执行我的“动作”函数感到有些茫然,只能让我的 Env 退出评估函数。

请注意,evalMotorInstruct 的当前代码不正确。该函数应该返回一个Env,但实际上返回一个IO Env

谢谢

【问题讨论】:

  • 你已经发现了 Haskell 的一个有趣的特性——它的纯度。您不能执行 IO 操作并返回 not 包装在 IO 中的结果。这是因为 Haskell 函数不能执行任意副作用;他们必须是纯洁的。根据您对doreturn 的使用,您可以返回IO Env,这似乎就是您在此处寻找的内容。
  • @AlexisKing 这是否意味着我应该将所有其他“eval”函数也转换为返回 IO Env?并在任何情况下“解开”它们?
  • 您可能想考虑使用免费的 monad 以避免提交到 IOhaskellforall.com/2012/06/…
  • @AlexisKing ,我尝试将代码弄乱一些,例如evalAssign 返回和接受IO Env。我没有成功:/。如果可能的话,您能否为我的问题提供一些示例/帮助。
  • 要想弄清楚如何保持纯代码的纯净并将IO 业务限制在外围需要一定数量的膝盖。稍后我可能会尝试写一个答案,但最好留给比我更有经验的老师。

标签: haskell io


【解决方案1】:

您的 AExpr 和 BExpr 类型代表您的语言中的纯计算 - 它们不仅不执行任何 IO,而且也不修改环境。因此,您不必修改它们的 eval 函数。

所以你只需要修改Stmt 值的评估。类型签名将更改为:

eval :: Stmt -> Env -> IO Env

eval Seq 将如何变化的示例:

eval (Seq []) env     = return env
eval (Seq (s:ss)) env = do env' <- eval s env  -- eval the first statement
                           eval (Seq ss) env'  -- eval the rest

注意eval If 不需要更改:

eval (If bool stmt1 stmt2) env = 
  if evalBExpr bool env
    then eval stmt1 env
    else eval stmt2 env

evalMotorInstruct 如果您将其签名更改为:

将编译
evalMotorInstruct :: String -> AExpr -> Env -> IO Env

剩下的交给你。

在重构时,只需将无法编译的代码注释掉即可。然后在添加另一行之前逐步将这些行逐一添加,让每行都进行编译。如果需要,请使用... = undefined。稍后回来填写。

【讨论】:

  • 一个潜在的改进是使用StateT Env IO作为monad,这样你就可以省略手动传递Env,你也可以写eval (Seq ss) = mapM_ eval ss等。跨度>
猜你喜欢
  • 2018-11-21
  • 2016-02-10
  • 1970-01-01
  • 1970-01-01
  • 2013-03-16
  • 2019-12-21
  • 1970-01-01
  • 2015-06-11
  • 1970-01-01
相关资源
最近更新 更多