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

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

我正在尝试为语法树算术运算创建一个函数,到目前为止我几乎已经达到了我想要的效果。在我附加的代码中,您可以看到我当前的函数定义。 eval 是决定每个操作要做什么的函数,foldAndPropagateConstants 是主要函数。 parse 是一个简单的解析器,它采用 String 的数学表达式和 returns 等效树。例如

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

我面临的问题是如何在后续操作中使用评估值。例如,此操作应按如下方式工作:

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

注意函数在计算"y"的值时应该使用"x"得到的值。问题是,我似乎找不到在 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 _ = []

编辑:我好像只回答了这部分问题:

I can't seem to find a way to use "x" value in my 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

data Expr 中的另一个构造函数扩展此求值器,并如 luqui 所建议的那样用 "environment" 扩展 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

在我看来,此求值器最紧迫的改进是使用错误感知 return 类型更好地处理错误。我制作了 lookup' 辅助函数,因为标准库函数 Data.List.lookup 使用更安全的 Maybe return 类型,这会鼓励我建议的重写:

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)

我在每个函数体中使用了不同的风格,但它们都是相似主题的变体:case-of 使用显式模式匹配,这变得乏味。 (想象一下使用 case-of 来处理 eval (BinOp ...) 主体。) >>= 运算符的显式使用是……我想有些人喜欢它,但是 do 符号看起来更漂亮。我认为 <$> applicative style 是本例中最简洁的。

接下来你可以做的是通过使用 Reader monad 使 env 隐式化:eval 中只有一个函数体实际使用它有点混乱,并且其他人要么扔掉要么传下去。

你想要的是为了

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

相当于

 =  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' 类似于 parse,但它还可以查询目前已知的值存储,并将其作为第二个参数接收。

如果重组为

,上面的代码更容易用函数编码
 =  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])

顺便说一句,这种 状态传递计算 的模式被称为状态单子,但这是另一天的事。

当表示为

时,以上符合递归模式
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, [])

现在,Generalize!(通过用符号值替换示例值)。