语言设计:解析表达式的求值在 Haskell 中如何工作?
Language design: How does evaluation of parsed expressions work in Haskell?
我正在用 Haskell 编写一种简单的语言,但我在计算算术和关系表达式时遇到了问题。起初我写了一个评估函数,然后我意识到我需要单独的函数来评估我的语言的各个方面。所以我有一个算术评价函数,一个关系评价函数,然后是一个通用评价函数。
这个问题是由于实际生成了一些要使用的东西而产生的,我认为这是因为无法将我的其他 eval 函数与我的主要 eval 函数相关联。但是,我不确定我是否正在错误地进行评估。我相信也许像 'foldl1' 这样的函数对于在评估中实施更有用。我最初是从 Write Yourself A Scheme in 48 Hours Wikibook 中了解到这个函数的,它似乎比评估每个可能的表达式更有用。我不确定这将如何在我当前的代码中实现,除非我对每个表达式都有一个函数。例如,如果我有一个像“1 + 1”这样的表达式,我当前的解析器将其解析为“1 加 1”,但是如果我使用 foldl1,我可以有一个函数来获取这个表达式的每个部分并且 "folds" 参数上的运算符。即使我要将我当前的评估函数与 foldl1 的想法合并,我仍然预见到在评估任何有意义的东西时会出现问题,而不仅仅是整数或字符串。
如有任何指导或帮助,我们将不胜感激。
下面我已经包含了数据类型和我的评估函数:
data HenryVal = Atom String
| String String
| Integer Integer
| Bool Bool
| Not HenryVal
| Neg HenryVal
| List [HenryVal]
| Seq [HenryVal]
| Assign String HenryVal
| If HenryVal HenryVal HenryVal
| While HenryVal HenryVal
| Skip
| ABinary ABinOp HenryVal HenryVal
| BBinary BBinOp HenryVal HenryVal
| RBinary RBinOp HenryVal HenryVal
data BBinOp = And | Or deriving (Show)
data RBinOp = Greater | Less deriving (Show)
data ABinOp = Add
| Subtract
| Multiply
| Divide
deriving (Show)
evalABinOP :: HenryVal -> ABinOp -> HenryVal -> HenryVal
evalABinOP (Integer a) Add (Integer b) = Integer (a + b)
evalABinOP (Integer a) Multiply (Integer b) = Integer (a * b)
evalABinOP (Integer a) Divide (Integer b) = Integer (a `div` b)
evalABinOP (Integer a) Subtract (Integer b) = Integer (a - b)
evalRBinOp :: HenryVal -> RBinOp -> HenryVal -> HenryVal
evalRBinOp (Integer a) Greater (Integer b) = if a > b then (Bool True) else (Bool False)
evalRBinOp (Integer a) Less (Integer b) = if a < b then (Bool True) else (Bool False)
evalStmt :: HenryVal -> [HenryVal]
evalStmt (Assign var val) = [val]
evalCond :: HenryVal -> Bool
evalCond (Bool cond) = if cond == True then True else False
eval :: HenryVal -> HenryVal
eval val@(Atom _) = val
eval val@(String _) = val
eval val@(Integer _) = val
eval val@(Bool _) = val
eval val@(Neg _) = val
eval val@(Not _) = val
eval (List [Atom "quote", val]) = val
eval val@(List _) = val
eval val@(Seq _) = val
eval (If cond a b) = if (evalCond cond) then (eval a) else (eval b)
eval (Assign var val) = eval val
eval (Seq (Atom func : args)) = apply func $ map eval args
eval (ABinary op x y) = evalABinOP x op y
eval (RBinary op x y) = evalRBinOp x op y
错误信息:
./hask "[4 + 4]"
"No match: "Henry" (line 1, column 4):
unexpected "+"
expecting space, "(", "if", ";Do", "skip", identifier, letter, digit, "\"" or "[""
在 48 小时内为自己编写方案:https://en.wikibooks.org/wiki/Write_Yourself_a_Scheme_in_48_Hours/Evaluation,_Part_1
我不确定使用 foldl1
对您有多大帮助。在 "Write Yourself a Scheme" wikibook 中,foldl1
函数用于将 "binary" 函数(如 +
和 -
应用于可能是长度 > 2。在 Lisp 中,(+ 1 2 3)
与 Haskell 表达式的含义相同:
foldl1 (+) [1,2,3]
所以为了这个特定目的,foldl1
是评估 Lisp 算术表达式的好方法。如果你有一个看起来像 ABinary ABinOp [HenryVal]
的 HenryVal
构造函数,并且你希望你的 list 参数具有相同的折叠行为,那么你可能需要使用 foldl1
.
那么,我们如何解决您 运行 遇到的问题。如果我可以冒险猜测,我想你可能正在看这样的表达式:
ABinary Add (ABinary Add (Integer 1) (Integer 2)) (Integer 3)
并意识到你的评估者无法处理它,因为 evalABinOp
没有任何情况下其中一个参数是另一个 ABinary
而不是 Integer
,对吧?
嗯,诀窍是使用递归。在 evalABinOp
中,递归地计算参数,确保它们是整数,然后 然后 进行算术运算。所以,像这样:
evalABinOp :: HenryVal -> ABinOp -> HenryVal -> HenryVal
evalABinOp e1 op e2
= let Integer v1 = eval e1
Integer v2 = eval e2
in Integer $ calc v1 op v2
where
calc a Add b = a + b
calc a Multiply b = a * b
calc a Divide b = a `div` b
calc a Subtract b = a - b
然后,将deriving (Show)
实例添加到HenryVal
后,您可以看到E:
> eval $ ABinary Add (ABinary Add (Integer 1) (Integer 2)) (Integer 3)
Integer 6
作为我刚刚想到的替代方案,您可以保留 evalABinOp
的原始定义,原样:
evalABinOp :: HenryVal -> ABinOp -> HenryVal -> HenryVal
evalABinOp (Integer a) Add (Integer b) = Integer (a + b)
evalABinOp (Integer a) Multiply (Integer b) = Integer (a * b)
evalABinOp (Integer a) Divide (Integer b) = Integer (a `div` b)
evalABinOp (Integer a) Subtract (Integer b) = Integer (a - b)
并改为修改相应的 eval
案例以使其递归:
eval (ABinary op x y) = evalABinOp (eval x) op (eval y)
这会产生同样的效果。
我正在用 Haskell 编写一种简单的语言,但我在计算算术和关系表达式时遇到了问题。起初我写了一个评估函数,然后我意识到我需要单独的函数来评估我的语言的各个方面。所以我有一个算术评价函数,一个关系评价函数,然后是一个通用评价函数。
这个问题是由于实际生成了一些要使用的东西而产生的,我认为这是因为无法将我的其他 eval 函数与我的主要 eval 函数相关联。但是,我不确定我是否正在错误地进行评估。我相信也许像 'foldl1' 这样的函数对于在评估中实施更有用。我最初是从 Write Yourself A Scheme in 48 Hours Wikibook 中了解到这个函数的,它似乎比评估每个可能的表达式更有用。我不确定这将如何在我当前的代码中实现,除非我对每个表达式都有一个函数。例如,如果我有一个像“1 + 1”这样的表达式,我当前的解析器将其解析为“1 加 1”,但是如果我使用 foldl1,我可以有一个函数来获取这个表达式的每个部分并且 "folds" 参数上的运算符。即使我要将我当前的评估函数与 foldl1 的想法合并,我仍然预见到在评估任何有意义的东西时会出现问题,而不仅仅是整数或字符串。
如有任何指导或帮助,我们将不胜感激。
下面我已经包含了数据类型和我的评估函数:
data HenryVal = Atom String
| String String
| Integer Integer
| Bool Bool
| Not HenryVal
| Neg HenryVal
| List [HenryVal]
| Seq [HenryVal]
| Assign String HenryVal
| If HenryVal HenryVal HenryVal
| While HenryVal HenryVal
| Skip
| ABinary ABinOp HenryVal HenryVal
| BBinary BBinOp HenryVal HenryVal
| RBinary RBinOp HenryVal HenryVal
data BBinOp = And | Or deriving (Show)
data RBinOp = Greater | Less deriving (Show)
data ABinOp = Add
| Subtract
| Multiply
| Divide
deriving (Show)
evalABinOP :: HenryVal -> ABinOp -> HenryVal -> HenryVal
evalABinOP (Integer a) Add (Integer b) = Integer (a + b)
evalABinOP (Integer a) Multiply (Integer b) = Integer (a * b)
evalABinOP (Integer a) Divide (Integer b) = Integer (a `div` b)
evalABinOP (Integer a) Subtract (Integer b) = Integer (a - b)
evalRBinOp :: HenryVal -> RBinOp -> HenryVal -> HenryVal
evalRBinOp (Integer a) Greater (Integer b) = if a > b then (Bool True) else (Bool False)
evalRBinOp (Integer a) Less (Integer b) = if a < b then (Bool True) else (Bool False)
evalStmt :: HenryVal -> [HenryVal]
evalStmt (Assign var val) = [val]
evalCond :: HenryVal -> Bool
evalCond (Bool cond) = if cond == True then True else False
eval :: HenryVal -> HenryVal
eval val@(Atom _) = val
eval val@(String _) = val
eval val@(Integer _) = val
eval val@(Bool _) = val
eval val@(Neg _) = val
eval val@(Not _) = val
eval (List [Atom "quote", val]) = val
eval val@(List _) = val
eval val@(Seq _) = val
eval (If cond a b) = if (evalCond cond) then (eval a) else (eval b)
eval (Assign var val) = eval val
eval (Seq (Atom func : args)) = apply func $ map eval args
eval (ABinary op x y) = evalABinOP x op y
eval (RBinary op x y) = evalRBinOp x op y
错误信息:
./hask "[4 + 4]"
"No match: "Henry" (line 1, column 4):
unexpected "+"
expecting space, "(", "if", ";Do", "skip", identifier, letter, digit, "\"" or "[""
在 48 小时内为自己编写方案:https://en.wikibooks.org/wiki/Write_Yourself_a_Scheme_in_48_Hours/Evaluation,_Part_1
我不确定使用 foldl1
对您有多大帮助。在 "Write Yourself a Scheme" wikibook 中,foldl1
函数用于将 "binary" 函数(如 +
和 -
应用于可能是长度 > 2。在 Lisp 中,(+ 1 2 3)
与 Haskell 表达式的含义相同:
foldl1 (+) [1,2,3]
所以为了这个特定目的,foldl1
是评估 Lisp 算术表达式的好方法。如果你有一个看起来像 ABinary ABinOp [HenryVal]
的 HenryVal
构造函数,并且你希望你的 list 参数具有相同的折叠行为,那么你可能需要使用 foldl1
.
那么,我们如何解决您 运行 遇到的问题。如果我可以冒险猜测,我想你可能正在看这样的表达式:
ABinary Add (ABinary Add (Integer 1) (Integer 2)) (Integer 3)
并意识到你的评估者无法处理它,因为 evalABinOp
没有任何情况下其中一个参数是另一个 ABinary
而不是 Integer
,对吧?
嗯,诀窍是使用递归。在 evalABinOp
中,递归地计算参数,确保它们是整数,然后 然后 进行算术运算。所以,像这样:
evalABinOp :: HenryVal -> ABinOp -> HenryVal -> HenryVal
evalABinOp e1 op e2
= let Integer v1 = eval e1
Integer v2 = eval e2
in Integer $ calc v1 op v2
where
calc a Add b = a + b
calc a Multiply b = a * b
calc a Divide b = a `div` b
calc a Subtract b = a - b
然后,将deriving (Show)
实例添加到HenryVal
后,您可以看到E:
> eval $ ABinary Add (ABinary Add (Integer 1) (Integer 2)) (Integer 3)
Integer 6
作为我刚刚想到的替代方案,您可以保留 evalABinOp
的原始定义,原样:
evalABinOp :: HenryVal -> ABinOp -> HenryVal -> HenryVal
evalABinOp (Integer a) Add (Integer b) = Integer (a + b)
evalABinOp (Integer a) Multiply (Integer b) = Integer (a * b)
evalABinOp (Integer a) Divide (Integer b) = Integer (a `div` b)
evalABinOp (Integer a) Subtract (Integer b) = Integer (a - b)
并改为修改相应的 eval
案例以使其递归:
eval (ABinary op x y) = evalABinOp (eval x) op (eval y)
这会产生同样的效果。