F#表达式树解释器实现

时间:2016-02-14 05:47:43

标签: tree f#

我正在尝试为可以在F#中添加或乘法的表达式树构建一个解释器,但是我遇到了一些麻烦。我也试图使用选项类型,以便如果变量不在表达式中,则解释器返回None。

这是我给出的代码。

type Exptree =
  | Const of int
  | Var of string
  | Add of Exptree * Exptree
  | Mul of Exptree * Exptree

type Bindings = (string * int) list

let rec eval(exp : Exptree, env:Bindings) =  
  match exp with
  |  

我对与exp匹配的内容感到困惑,因为有很多选项。我的想法是看看Add和Mul,并尝试在每个递归步骤中将其删除,直到我得到一个整数,但是我完全迷失了。

我能做点什么吗?

match exp with
| Some(Add) -> int + int?
| Some(Mul) -> int * int?

2 个答案:

答案 0 :(得分:6)

您的Exptree类型有四种可能的构造函数:Const,Var,Add和Mul。所以,你的匹配将是那些:

match exp with
| Const c -> 
| Var s ->
| Add (e1,e2) -> 
| Mul (e1,e2) ->

对于每种情况,您都会执行相应的操作(在环境中添加,乘法,查找变量,返回常量)。在add和mul的情况下,你会解释子表达式(e1和e2)上eval的递归调用的结果,看看值是None还是Some(s),并相应地对它进行操作。

答案 1 :(得分:4)

因此,scrwtp是正确的,Map<string, int>对您的绑定集更有意义,因为您可以在O(log n)时间内执行查找,而不是在list的O(n)时间内执行查找。

eval功能的第一部分很简单:

  1. 如果值是常量,我们只需要返回该常量的Some
  2. 如果值是变量,我们需要在地图中查找,如果密钥存在,我们返回值的Some。如果它们不存在,我们返回None。我们可以使用Map.tryFind来执行此操作。
  3. 我们可以这样做:

    let rec eval bindings tree =
        match tree with
        |Const i -> Some i
        |Var varName -> Map.tryFind varName bindings
    

    执行加法和乘法需要更多考虑。为了抽象组合两个子树,有意义的是定义一个函数,该函数将两个选项作为参数,并允许您将两个结果的函数应用于Some value。这称为lift2函数,我们可以为Option

    定义它
    /// Combines two option values using a supplied function
    let lift2 f opt1 opt2 =
        match (opt1, opt2) with
        |Some val1, Some val2 -> Some <| f val1 val2
        |_ -> None
    

    有关lift系列函数的更多信息,请参阅此精彩教程:http://fsharpforfunandprofit.com/posts/elevated-world/#lift

    现在我们可以通过调用此函数来扩展我们的eval函数以用于加法和乘法。

    /// Evaluates the result of an expression tree
    let rec eval bindings tree =
        match tree with
        |Const i -> Some i // Const always returns some value
        |Var varName -> Map.tryFind varName bindings // Returns some value if it exists in the binding table
        |Add (tree1, tree2) ->
            lift2 (+) (eval bindings tree1) (eval bindings tree2) // add expressions if both expressions return Some val
        |Mul (tree1, tree2) ->
            lift2 (*) (eval bindings tree1) (eval bindings tree2) // multiply expressions if both expressions return Some val
    

    现在,添加案例使用lift2函数来说明,如果两个子表达式都计算为实数值,则将结果一起添加,否则返回None

    乘法情况遵循完全相同的逻辑,我们刚刚交换了运算符。

    快速搁置:正如GuyCoder所指出的,构建绑定表的一种便捷方法是使用语法:

    let bindings = Map.ofList [("a",1); ("b",2); ("c",3)]