语言设计:混淆为什么分配的变量 returns 在算术表达式中是错误的答案
Language Design: Confusion as to why an assigned variable returns the wrong answer in arithmetic expressions
编辑:为澄清起见,所有 integer op integer
形式的算术表达式都可以正常工作,但 variable op integer
会导致令人困惑的结果。
在我编写的简单语言中,我正处于尝试实现用户定义变量的阶段。我正在按照 'Write Yourself a Scheme in 48 Hours' 指南构建我的开发过程。我已经成功地实现了变量实例化,这样我的表达式 x:=4
的行为就像 WYS48 的 (define x 4)
一样,因为它们都是 return 4.
然而,当我尝试在算术表达式中使用 x
时,我得到了一个不寻常的结果。
Henry > x:=4
4
Henry > <x+4>
76
起初我以为它是将 x 的 ASCII 值加到 4,但事实并非如此,因为 x 的 ASCII 值是 120 而它的十六进制是 78,所以我知道不是这样。但是,我看不出程序中的错误在哪里。我怀疑它可能在我的函数 str2Int
中,主要是因为它减去 48。下面是我用来评估算术、表达式和函数的代码,我用来评估我的 x
。当表达式为 x:=0
时减去 71 似乎适用于这种情况算术对于 x
的实例化很好,但它不是一个特别最佳的修复。
eval :: Env -> HenryVal -> IOThrowsError HenryVal
eval env val@(Atom _) = return val
eval env val@(ABinOp _) = return val
eval env (Assign var val) = eval env val >>= defineVar env var
eval env (ABinary op x y) = return $ evalABinOp env x op y
evalABinOp :: Env -> HenryVal -> ABinOp -> HenryVal -> HenryVal
evalABinOp env (Atom a) Add (Integer b) = Integer ((toInteger (str2Int'(str2HenryStr (a)))) + b )
str2Int' :: HenryVal -> Integer
str2Int' n = toInteger $ (ord (show n !! 1)) - 48
str2HenryStr :: String -> HenryVal
str2HenryStr s = String $ s
我不确定它是否相关,但下面是我用来实现变量赋值的代码
type Env = IORef [(String, IORef HenryVal)]
type ThrowsError = Either HenryError
type IOThrowsError = ExceptT HenryError IO
nullEnv :: IO Env
nullEnv = newIORef []
liftThrows :: ThrowsError a -> IOThrowsError a
liftThrows (Left err) = throwError err
liftThrows (Right val) = return val
runIOThrows :: IOThrowsError String -> IO String
runIOThrows action = runExceptT (trapError action) >>= return . extractValue
isBound :: Env -> String -> IO Bool
isBound envRef var = readIORef envRef >>= return . maybe False (const True) . lookup var
getVar :: Env -> String -> IOThrowsError HenryVal
getVar envRef var = do env <- liftIO $ readIORef envRef
maybe (throwError $ UnboundVar "Getting an unbound variable" var)
(liftIO . readIORef)
(lookup var env)
setVar :: Env -> String -> HenryVal -> IOThrowsError HenryVal
setVar envRef var value = do env <- liftIO $ readIORef envRef
maybe (throwError $ UnboundVar "Setting an unbound variable" var)
(liftIO . (flip writeIORef value))
(lookup var env)
return value
defineVar :: Env -> String -> HenryVal -> IOThrowsError HenryVal
defineVar envRef var value = do
alreadyDefined <- liftIO $ isBound envRef var
if alreadyDefined
then setVar envRef var value >> return value
else liftIO $ do
valueRef <- newIORef value
env <- readIORef envRef
writeIORef envRef ((var, valueRef) : env)
return value
bindVars :: Env -> [(String, HenryVal)] -> IO Env
bindVars envRef bindings = readIORef envRef >>= extendEnv bindings >>= newIORef
where extendEnv bindings env = liftM (++ env) (mapM addBinding bindings)
addBinding (var, value) = do ref <- newIORef value
return (var, ref)
除非您有 eval
的任何其他实现,否则任何变量都不会被计算为二元运算的值(赋值除外)。让我们看看 eval
和 evalABinOp
:
eval :: Env -> HenryVal -> IOThrowsError HenryVal
eval env val@(Atom _) = return val
eval env val@(ABinOp _) = return val
eval env (Assign var val) = eval env val >>= defineVar env var -- defineVar returns val
eval env (ABinary op x y) = return $ evalABinOp env x op y -- evalABinOp uses env?
evalABinOp :: Env -> HenryVal -> ABinOp -> HenryVal -> HenryVal
evalABinOp env (Atom a) Add (Integer b) = Integer ((toInteger (str2Int'(str2HenryStr (a)))) + b )
-- env is not used on the right hand side!
由于您从不在右侧使用 env
,我们可以 100% 确定 Atom a
不能解释为变量,而必须解释为数字或字符串 (或作为未定义的变量)。在您的代码中,您实际上没有在当前环境中查找 a
的值。相反,您将 x
转换为整数:
str2Int' :: HenryVal -> Integer
str2Int' n = toInteger $ (ord (show n !! 1)) - 48
str2HenryStr :: String -> HenryVal
str2HenryStr s = String $ s
我手边既没有 HenryVal
的定义,也没有它的 Show
实例,所以我只能假设 show (String "x")
导致 "\"x\""
,因此 [=24] =]. ord 'x' - 48
是 120 - 48 = 72
。添加 4
,最后得到 76
。算法按预期工作。 您只是根本不使用分配的值。相反,您甚至不将 Atom
解释为变量,而是解释为一位数的 ASCII 数字。
话虽如此,解决方案会是什么样子?好吧,类似的东西:
evalABinOp :: Env -> HenryVal -> ABinOp -> HenryVal -> IOThrowsError HenryVal
evalABinOp env (Integer a) Add (Integer b) = return $ Integer $ a + b
evalABinOp env (Atom a) op b@(Integer _) = getVar env a >>= (\c -> evalABinOp env c op b)
我们没有立即使用 a
,而是先查找值。请注意,这仅在您的解析器将数字解释为 HenryVal
语言中的 Integer
时才有效。请注意,没有 str2Int
魔术:所有这些都应该在我们使用 x
之前处理好,除非我们想允许添加字符串。
编辑:为澄清起见,所有 integer op integer
形式的算术表达式都可以正常工作,但 variable op integer
会导致令人困惑的结果。
在我编写的简单语言中,我正处于尝试实现用户定义变量的阶段。我正在按照 'Write Yourself a Scheme in 48 Hours' 指南构建我的开发过程。我已经成功地实现了变量实例化,这样我的表达式 x:=4
的行为就像 WYS48 的 (define x 4)
一样,因为它们都是 return 4.
然而,当我尝试在算术表达式中使用 x
时,我得到了一个不寻常的结果。
Henry > x:=4
4
Henry > <x+4>
76
起初我以为它是将 x 的 ASCII 值加到 4,但事实并非如此,因为 x 的 ASCII 值是 120 而它的十六进制是 78,所以我知道不是这样。但是,我看不出程序中的错误在哪里。我怀疑它可能在我的函数 str2Int
中,主要是因为它减去 48。下面是我用来评估算术、表达式和函数的代码,我用来评估我的 x
。当表达式为 x:=0
时减去 71 似乎适用于这种情况算术对于 x
的实例化很好,但它不是一个特别最佳的修复。
eval :: Env -> HenryVal -> IOThrowsError HenryVal
eval env val@(Atom _) = return val
eval env val@(ABinOp _) = return val
eval env (Assign var val) = eval env val >>= defineVar env var
eval env (ABinary op x y) = return $ evalABinOp env x op y
evalABinOp :: Env -> HenryVal -> ABinOp -> HenryVal -> HenryVal
evalABinOp env (Atom a) Add (Integer b) = Integer ((toInteger (str2Int'(str2HenryStr (a)))) + b )
str2Int' :: HenryVal -> Integer
str2Int' n = toInteger $ (ord (show n !! 1)) - 48
str2HenryStr :: String -> HenryVal
str2HenryStr s = String $ s
我不确定它是否相关,但下面是我用来实现变量赋值的代码
type Env = IORef [(String, IORef HenryVal)]
type ThrowsError = Either HenryError
type IOThrowsError = ExceptT HenryError IO
nullEnv :: IO Env
nullEnv = newIORef []
liftThrows :: ThrowsError a -> IOThrowsError a
liftThrows (Left err) = throwError err
liftThrows (Right val) = return val
runIOThrows :: IOThrowsError String -> IO String
runIOThrows action = runExceptT (trapError action) >>= return . extractValue
isBound :: Env -> String -> IO Bool
isBound envRef var = readIORef envRef >>= return . maybe False (const True) . lookup var
getVar :: Env -> String -> IOThrowsError HenryVal
getVar envRef var = do env <- liftIO $ readIORef envRef
maybe (throwError $ UnboundVar "Getting an unbound variable" var)
(liftIO . readIORef)
(lookup var env)
setVar :: Env -> String -> HenryVal -> IOThrowsError HenryVal
setVar envRef var value = do env <- liftIO $ readIORef envRef
maybe (throwError $ UnboundVar "Setting an unbound variable" var)
(liftIO . (flip writeIORef value))
(lookup var env)
return value
defineVar :: Env -> String -> HenryVal -> IOThrowsError HenryVal
defineVar envRef var value = do
alreadyDefined <- liftIO $ isBound envRef var
if alreadyDefined
then setVar envRef var value >> return value
else liftIO $ do
valueRef <- newIORef value
env <- readIORef envRef
writeIORef envRef ((var, valueRef) : env)
return value
bindVars :: Env -> [(String, HenryVal)] -> IO Env
bindVars envRef bindings = readIORef envRef >>= extendEnv bindings >>= newIORef
where extendEnv bindings env = liftM (++ env) (mapM addBinding bindings)
addBinding (var, value) = do ref <- newIORef value
return (var, ref)
除非您有 eval
的任何其他实现,否则任何变量都不会被计算为二元运算的值(赋值除外)。让我们看看 eval
和 evalABinOp
:
eval :: Env -> HenryVal -> IOThrowsError HenryVal
eval env val@(Atom _) = return val
eval env val@(ABinOp _) = return val
eval env (Assign var val) = eval env val >>= defineVar env var -- defineVar returns val
eval env (ABinary op x y) = return $ evalABinOp env x op y -- evalABinOp uses env?
evalABinOp :: Env -> HenryVal -> ABinOp -> HenryVal -> HenryVal
evalABinOp env (Atom a) Add (Integer b) = Integer ((toInteger (str2Int'(str2HenryStr (a)))) + b )
-- env is not used on the right hand side!
由于您从不在右侧使用 env
,我们可以 100% 确定 Atom a
不能解释为变量,而必须解释为数字或字符串 (或作为未定义的变量)。在您的代码中,您实际上没有在当前环境中查找 a
的值。相反,您将 x
转换为整数:
str2Int' :: HenryVal -> Integer
str2Int' n = toInteger $ (ord (show n !! 1)) - 48
str2HenryStr :: String -> HenryVal
str2HenryStr s = String $ s
我手边既没有 HenryVal
的定义,也没有它的 Show
实例,所以我只能假设 show (String "x")
导致 "\"x\""
,因此 [=24] =]. ord 'x' - 48
是 120 - 48 = 72
。添加 4
,最后得到 76
。算法按预期工作。 您只是根本不使用分配的值。相反,您甚至不将 Atom
解释为变量,而是解释为一位数的 ASCII 数字。
话虽如此,解决方案会是什么样子?好吧,类似的东西:
evalABinOp :: Env -> HenryVal -> ABinOp -> HenryVal -> IOThrowsError HenryVal
evalABinOp env (Integer a) Add (Integer b) = return $ Integer $ a + b
evalABinOp env (Atom a) op b@(Integer _) = getVar env a >>= (\c -> evalABinOp env c op b)
我们没有立即使用 a
,而是先查找值。请注意,这仅在您的解析器将数字解释为 HenryVal
语言中的 Integer
时才有效。请注意,没有 str2Int
魔术:所有这些都应该在我们使用 x
之前处理好,除非我们想允许添加字符串。