Christian Ivicevic
Christian Ivicevic

Reputation: 10885

F# types and dynamic evaluation for basic computations

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

Answers (3)

Matt Fenwick
Matt Fenwick

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

Tomas Petricek
Tomas Petricek

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

BLUEPIXY
BLUEPIXY

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

Related Questions