Reputation: 19
I'm trying to construct a interpreter for an expression tree that can add or multiply in F#, but I'm having some trouble. I'm also trying to use option types so that the interpreter returns None if the variable isn't in the expression.
Here is the code I'm given.
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
|
I'm confused on what to match exp with, since there are so many options. My idea is to look at Add and Mul, and try to strip it down on each recursion step until I get a integer, however I am completely lost.
Could I do something like?
match exp with
| Some(Add) -> int + int?
| Some(Mul) -> int * int?
Upvotes: 1
Views: 610
Reputation: 12184
So, scrwtp is correct that Map<string, int>
makes more sense for your set of bindings because you can then perform lookup in O (log n) time, rather than O (n) time with list
.
The first part of the eval
function is easy:
Some
of that constant.Some
of the value. If they key does not exist, we return None
. We can use Map.tryFind
to do that.We can do this part like this:
let rec eval bindings tree =
match tree with
|Const i -> Some i
|Var varName -> Map.tryFind varName bindings
Performing addition and multiplication requires a bit more thought. In order to abstract combining two sub-trees, it makes sense to define a function which takes two options as arguments and allows you to apply a function to both results if they are both Some value
. This is called the lift2
function and we can define it for 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
For more on the lift
family of functions, see this great tutorial: http://fsharpforfunandprofit.com/posts/elevated-world/#lift
Now we can extend our eval
function for the Addition and Multiplication cases by calling this function.
/// 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
Now, the add case is using the lift2
function to say, if both sub-expressions evaluate to a real value, add the results together, otherwise to return None
.
The multiplication case follows exactly the same logic, we've just swapped the operator.
A quick aside: As GuyCoder points out, a convenient way to build your binding table would then be to use the syntax:
let bindings = Map.ofList [("a",1); ("b",2); ("c",3)]
Upvotes: 4
Reputation: 191
Your Exptree type has four possible constructors: Const, Var, Add, and Mul. So, your match would be on those:
match exp with
| Const c ->
| Var s ->
| Add (e1,e2) ->
| Mul (e1,e2) ->
For each of those cases, you would do the appropriate operation (add, multiply, lookup a variable in the environment, return a constant). In the add and mul cases, you would interpret the result of the recursive invocation of eval on the subexpressions (e1 and e2) to see if the value is None or Some(s), and act on it accordingly.
Upvotes: 6