Reputation: 10885
Trying to get used to F# I tried small examples and my next step is to write a few functions for logical computations/evaluations and for now I have this structure
type Expr =
| True
| False
| Not of Expr
| And of Expr * Expr
and it should be obvious what I want to achieve: to be able to encapsulate different functions like Not
and And
to be used in one computation like And(And(True, Not(False)), True)
but currently I do not understand (this is my first time wrinting such "complex" functional code) how to use this struct and write the proper methods as for example i can write for evaluation
let evalLogic (expr : Expr) : bool =
match expr with
| True -> true
| False -> false
| _ -> false (* eval the expression which is not "atomic" *)
and then as a method
let And (left : Expr , right : Expr ) : Expr =
let value = evalLogic(left) && evalLogic(right)
match value with
| true -> True
| false -> False
however I know that this is crap and not the correct way of implementing this! Can you give me a hint how to achieve the desired behavior and describe how to extend this?
Upvotes: 1
Views: 244
Reputation: 49085
Is your question "how do I extend evalLogic
for Not
and And
?"
If so, then you need to extend the pattern-matching in the let-expression to match the other two constructors:
let evalLogic (expr : Expr) : bool =
match expr with
| True -> true
| False -> false
| Not (myexpr) -> ... (* match constructor `Not`, bind the expression to `myexpr`)
| And (lefte, righte) -> ...
Then you can fill in the right-hand sides with some F# code.
Here's some information on pattern-matching in F#.
Upvotes: 2
Reputation: 243051
When writing functional code to represent and evaluate expressions, the typical pattern is to define the type of expression as a discriminated union (just like you did!) and then write a function that takes the expression and returns the result. Your logical expressions will evaluate to boolean, so you need a function:
evalLogic : Expr -> bool
In the function, you need to write a case for every possible expression. Your sample handles False
and True
and the code by Bluepixy shows the rest. The general idea is that the function can recursively evaluate all sub-expressions - if you have And(e1, e2)
, then you can evaluate e1
and e2
to two boolean values and then combine them (using &&
).
Your And
function is not needed, because the whole evaluation is all done in the evalLogic
function. This is a typical way of doing things in the functional style (because the expression type is not expected to change frequently). Howeever, you could also support more binary operations using:
type Expr =
| Constant of bool
| Unary of Expr * (bool -> bool)
| Binary of Expr * Expr * (bool -> bool -> bool)
Here, the idea is that an expression is either a constant or an application of some binary or unary logical operator. For example, to represent true && not false
, you could write:
Binary(Constant(true), Unary(Constant(false), not), (&&))
Now the expression tree also carries the function that should be used, so the evaluation just needs to call the function (and you can use all standard logical operators easily). For example, the case for Binary
looks like this:
let rec evalLogic e =
// pattern matching
| Binary(e1, e2, op) -> op (evalLogic e1) (evalLogic e2)
Upvotes: 6
Reputation: 40145
let rec evalLogic (expr : Expr) : bool =
match expr with
| True -> true
| False -> false
| Not (exp ) -> not (evalLogic exp)
| And (expL, expR) -> evalLogic expL && evalLogic expR
DEMO
> evalLogic <| And(And(True, Not(False)), True);;
val it : bool = true
Upvotes: 3