【问题标题】:Haskell - Using previously computed values in next function calls in a sequenceHaskell - 在序列中的下一个函数调用中使用先前计算的值
【发布时间】:2020-03-28 13:39:44
【问题描述】:

我正在尝试为语法树算术运算创建一个函数,到目前为止,我几乎达到了我想要的位置。在我附加的代码中,您可以看到我当前的函数定义。 eval 是决定如何处理每个操作的函数,foldAndPropagateConstants 是主要函数。 parse 是一个简单的解析器,它接受数学表达式的 String 并返回等效的树。例如

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 _ = []

【问题讨论】:

  • 通常这是通过将“环境”参数添加到eval 来完成的,该参数存储所有变量的值,如Data.Map 或类似名称。
  • 可能应该指出我对 Haskell 很陌生。不知道该怎么做。 @luqui
  • 向函数的类型和定义添加参数是您可以通过阅读任意数量的资源来学习的内容。
  • 正如@luqui 所说,最好使用List 或Map 等环境,在其中存储变量及其值

标签: list function parsing haskell evaluation


【解决方案1】:

编辑:我似乎只回答了这部分问题:

我似乎找不到在我的 eval 函数中使用“x”值的方法。

由于您的问题不包含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 建议的“环境”扩展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

在我看来,这个评估器最紧迫的改进是使用错误感知返回类型更好地处理错误。我创建了 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)

我在每个函数体中使用了不同的样式,但它们都是相似主题的变体:case-of 使用显式模式匹配,这很乏味。 (想象一下使用 case-of 来处理 eval (BinOp ...) 正文。)显式使用 &gt;&gt;= 运算符是……我想有些人喜欢它,但 do 表示法看起来更漂亮。 &lt;$&gt; applicative style 在这种情况下是最简洁的,我认为。

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

【讨论】:

  • 整洁。我还没有完全理解它,但我可以问你为什么使用 evalBinOp 吗?是不是像一个附加功能?
  • 所以,到目前为止,我想我了解您的解决方案,我必须说它非常简洁。我面临的一个困惑是如何在foldAndPorpagateConstants 函数的定义中传递第二个变量。我的定义是:foldAndPropagateConstants (x:xs) = [(fst x, parse (show (eval(snd x)))) ] : foldAndPropagateConstants xs 给我一个错误,我认为这是因为 eval 在修改它以具有“环境”变量后的参数太少。
  • @MoodMojo:evalBinOp 只是为了避免直接在eval 中的四种情况,就像UnOp 中的三种情况一样。这只是一个方便的问题。您询问的有关foldAnd... 的问题超出了此答案的范围。在 cmets 中提出需要长时间回答的后续问题在 StackOverflow 上不起作用。
  • 当然。您的回答确实涵盖了您所说的内容,非常感谢。
【解决方案2】:

你想要的是什么

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!(通过将示例值替换为符号值)。

【讨论】:

  • 但是它将如何实际计算表达式的值?您似乎没有在您的解决方案中为此目的使用辅助函数,例如 eval,所以我不确定您是如何做到这一点的。
  • 这里我没有详细说明。只是关心代码的一般结构,因为您问如何在后续计算中使用先前计算的值(这就是我解释您的问题的方式)。
  • 所以这个答案是luqui's comment的扩展。
  • 啊,我明白了。再次感谢,威尔。 :)
  • 我会尝试使用你们解释的内容,但为了确保我理解正确,您正在制作另一个函数,即foldAndPropagateConstants' 来保持luqui 建议的环境变量,对吧?与parse' 相同
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-10-12
  • 2020-05-24
  • 2015-09-03
  • 1970-01-01
  • 2022-12-18
  • 2012-04-23
  • 1970-01-01
相关资源
最近更新 更多