简体   繁体   English

Haskell - 在序列中的下一个函数调用中使用先前计算的值

[英]Haskell - Using previously computed values in next function calls in a sequence

I'm trying to make a function for syntax tree arithmetic operations and thus far I'm almost where I want it to be.我正在尝试为语法树算术运算制作一个函数,到目前为止,我几乎达到了我想要的效果。 In the code I attach you can see my current function definitions.在我附加的代码中,您可以看到我当前的函数定义。 eval is the function that decides what to do with each operation and foldAndPropagateConstants is the main function. eval是决定如何处理每个操作的函数,而foldAndPropagateConstants是主要函数。 parse is a simple parser that takes a String of a mathematical expression and returns the equivalent tree. parse是一个简单的解析器,它接受一个数学表达式的String并返回等效的树。 eg例如

ghci> parse "3+x"
BinaryOperation Plus (Leaf (Constant 3)) (Leaf (Variable "x"))

The issue I'm facing is how to have the evaluated values to be used in subsequent operations.我面临的问题是如何在后续操作中使用评估值。 For example, this operation should work as follows:例如,此操作应按如下方式工作:

ghci> foldAndPropagateConstants [("x", parse "1+2+3"), ("y", parse "5*x + 7")]
[("x",Leaf (Constant 6)),("y",Leaf (Constant 37))]

Notice that the function should use the value that "x" got, when calculating the value for "y" .请注意,在计算"y"的值时,该函数应使用"x"获得的值。 Thing is, I can't seem to find a way to use "x" 's value in my eval function.问题是,我似乎找不到在我的eval函数中使用"x"值的方法。

--foldAndPropagateConstants :: [(String, Exprv)] -> [(String, ExprV)]

eval :: ExprV -> Int
eval (Leaf (Variable n)) = --this part is what's missing
eval (Leaf (Constant n)) = n
eval (BinaryOperation Plus expr1 expr2) = eval expr1 + eval expr2
eval (BinaryOperation Times expr1 expr2) = eval expr1 * eval expr2
eval (UnaryOperation Minus expr1) = -1 * eval expr1

foldAndPropagateConstants (x:xs) = [(fst x, parse (show (eval(snd x)))) ] : foldAndPropagateConstants xs
foldAndPropagateConstants _ = []

Edit: I seem to have answered only this part of the question:编辑:我似乎只回答了问题的这一部分:

I can't seem to find a way to use "x" value in my eval function.我似乎找不到在我的 eval 函数中使用“x”值的方法。

Since your question does not include a Minimal, Reproducible Example , here is a simplified version of what you seem to be doing (that doesn't contain variables) that both contains the data definition and the eval function:由于您的问题不包括Minimal, Reproducible Example ,这里是您似乎正在做的事情的简化版本(不包含变量),其中包含data定义和eval函数:

module Eval where

data Expr
  = Constant Int
  | UnOp UnaryOperation Expr
  | BinOp BinaryOperation Expr Expr
  deriving (Eq, Show)

data UnaryOperation
  = UnaryMinus
  | UnaryFactorial
  | UnaryAbsolute
  deriving (Eq, Show)

data BinaryOperation
  = Plus
  | Minus
  | Times
  | Divide
  deriving (Eq, Show)

eval :: Expr -> Int
eval (Constant n) = n
eval (UnOp UnaryMinus e) = negate (eval e)
eval (UnOp UnaryFactorial e) = product [1..eval e]
eval (UnOp UnaryAbsolute e) = abs (eval e)
eval (BinOp bop e1 e2) = evalBinOp bop (eval e1) (eval e2)

evalBinOp :: BinaryOperation -> Int -> Int -> Int
evalBinOp Plus = (+)
evalBinOp Minus = (-)
evalBinOp Times = (*)
evalBinOp Divide = div

Extending this evaluator with another constructor in data Expr , and extending the eval function with an "environment" as luqui suggests, which in this case is a list of name-value pairs:使用data Expr另一个构造函数扩展此评估器,并使用 luqui 建议的“环境”扩展eval函数,在这种情况下,它是名称-值对的列表:

data Expr
  = Constant Int
  | Variable String
  | UnOp UnaryOperation Expr
  | BinOp BinaryOperation Expr Expr
  deriving (Eq, Show)

-- ...

eval :: Expr -> [(String, Int)] -> Int
eval (Constant n) _env = n
eval (Variable s) env = lookup' s env
eval (UnOp UnaryMinus e) env = negate (eval e env)
eval (UnOp UnaryFactorial e) env = product [1..eval e env]
eval (UnOp UnaryAbsolute e) env = abs (eval e env)
eval (BinOp bop e1 e2) env = evalBinOp bop (eval e1 env) (eval e2 env)

-- ...

lookup' :: String -> [(String, Int)] -> Int
lookup' s [] = error ("Could not find variable " ++ s)
lookup' s ((t,n):env)
  | s == t = n
  | otherwise = lookup' s env

It seems to me that the most urgent improvement to this evaluator is better error handling using error-aware return types.在我看来,这个评估器最紧迫的改进是使用错误感知返回类型更好地处理错误。 I made the lookup' helper function because the standard library function Data.List.lookup uses the safer Maybe return type which would encourage the rewrite I'm suggesting:我创建了lookup'辅助函数,因为标准库函数Data.List.lookup使用更安全的Maybe返回类型,这会鼓励我建议的重写:

eval :: Expr -> [(String, Int)] -> Maybe Int
eval (Constant n) _env = pure n
eval (Variable s) env = lookup s env
eval (UnOp UnaryMinus e) env =
  case eval e env of
    Just n -> pure (negate n)
    Nothing -> Nothing
eval (UnOp UnaryFactorial e) env =
  eval e env >>= \n ->
  pure (product [1..n])
eval (UnOp UnaryAbsolute e) env =
  abs <$> eval e env
eval (BinOp bop e1 e2) env = do
  n1 <- eval e1 env
  n2 <- eval e2 env
  pure (evalBinOp bop n1 n2)

I've used a different style in each function body, but they're all variations of a similar theme: case-of uses explicit pattern matching, which gets tedious.我在每个函数体中使用了不同的样式,但它们都是类似主题的变体:case-of 使用显式模式匹配,这会变得乏味。 (Imagine doing the eval (BinOp ...) body using case-of.) Explicit use of the >>= operator is... I suppose some people like it, but do notation looks prettier. (想象一下在做eval (BinOp ...)使用案例的身体。)显式使用的>>=运算符...我想有些人喜欢它,但是do记号看起来更漂亮。 The <$> applicative style is the neatest of them all in this case, I think.我认为在这种情况下<$> 应用程序样式是最简洁的。

What you could do next is make the env implicit by using the Reader monad: It is a bit messy that only one function body in eval actually uses it, and all the others either throw it away or pass it along.您接下来可以做的是使用Reader monad 使env隐式: eval中只有一个函数体实际使用它,而所有其他函数体要么将其丢弃,要么传递它,这有点混乱。

What you want is for你想要的是为了

foldAndPropagateConstants [("x", parse "1+2+3"), ("y", parse "5*x + 7"), ("z", parse "x+y-1")]

to be equivalent to相当于

 =  let s0 = []

        r1 = parse' "1+2+3" s0
        -- r1 = Leaf (Constant 6)
        s1 = [("x",6)]

        r2 = parse' "5*x + 7" s1 
        -- r2 = Leaf (Constant 37)
        s2 = [("x",6),("y",37)]

        r3 = parse' "x+y-1" s2 
        -- r3 = Leaf (Constant 42)
        s3 = [("x",6),("y",37),("z",42)]
    in
       [r1,r2,r3]

parse' is like parse , but it can also consult a store of values known so far which it receives as its second argument. parse'类似于parse ,但它也可以查询迄今为止已知的值存储,并将其作为第二个参数接收。

The above is easier encoded with functions if restructured as如果重组为,上面的函数更容易编码

 =  let s0 = []

        (s1, r1) = parse'' "1+2+3" s0
        -- r1 = Leaf (Constant 6)
        -- s1 = [("x",6)]

        (s2, r2) = parse'' "5*x + 7" s1 
        -- r2 = Leaf (Constant 37)
        -- s2 = [("x",6),("y",37)]

        (s3, r3) = parse'' "x+y-1" s2 
        -- r3 = Leaf (Constant 42)
        -- s3 = [("x",6),("y",37),("z",42)]
    in
       snd (s3, [r1,r2,r3])

Incidentally this pattern of state-passing computations is known as State Monad, but that's a matter for another day.顺便提一下,这种状态传递计算模式被称为状态 Monad,但那是另一天的事了。

The above fits the recursion pattern when expressed as当表示为时,以上符合递归模式

foldAndPropagateConstants [("x", parse "1+2+3"), ("y", parse "5*x + 7"), ("z", parse "x+y-1")] = 
    snd $ foldAndPropagateConstants' 
            [("x", parse "1+2+3"), ("y", parse "5*x + 7"), ("z", parse "x+y-1")] 
            []

foldAndPropagateConstants' [("x", parse "1+2+3"), ("y", parse "5*x + 7"), ("z", parse "x+y-1")] s0 =
    let 
        (s1, r1) = parse'' "1+2+3" s0
        (sn, rs) = foldAndPropagateConstants' [("y", parse "5*x + 7"), ("z", parse "x+y-1")] s1 
    in
       (sn, r1 : rs)

-- and

foldAndPropagateConstants' [] s0 = (s0, [])

And now, Generalize!现在,概括! (by replacing an example value with a symbolic one). (通过用符号值替换示例值)。

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

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