2
votes

Recently, I started learning F# and I am a bit struggling with discriminated unions and function signatures.

I am trying to define an arithmetic expression (for sum, product, multiply, average etc) as a discriminate union and write a function that evaluates it. However, I can't figure out what I am doing wrong. Could anyone point me in the right direction? This what I've tried so far:

Attempt 1

type Expr = 
    | Sum of int * int
    | Avg of int * int
    | Mul of int * int

let Evaluate (input : Expr)  =
    match input with
    | Sum(a,b) -> a + b

printfn "%A" (Sum(5,10))

My output is :

Sum (5,10)

Attempt 2

I also tried something like :

type Expr = int -> int -> int

let evaluate (a : Expr) (b : Expr) = 
    match a,b with
    | a,b -> (fun a -> a + b)

printfn "%A" (evaluate 5 10)

since all common arithmetic expressions (like sum, product, multiply, average etc) take two integer inputs and output a single integer.

I am getting errors for: 'This expression was expected to have type Expr but here has type Int'.

edit 1

let evaluate (input : Expr)  =
    match input with
    | Sum(a,b) -> a + b
    | Avg(a,b) -> a + b / 2
    | Mul(a,b) -> a * b

let evaluate = function
    | Sum(a,b) -> a + b
    | Avg(a,b) -> a + b / 2
    | Mul(a,b) -> a * b
2
You aren't calling Evaluate in your first example: printfn "%i" (Evaluate (Sum(5,10))) - Lee
Thanks! That worked for attempt 1. How would I achieve the same result using attempt 2 with int -> int -> int? - Josh Williams
You don't have a discriminated union in your second example, it's just an alias of a function type. What's the expected return type of evaluate in that case? - Lee

2 Answers

4
votes

Your first attempt comes close.

type Expr = 
    | Sum of int * int
    | Avg of int * int
    | Mul of int * int

let evaluate = function
    | Sum(a,b) -> a + b
    | Avg(a,b) -> a + b / 2
    | Mul(a,b) -> a * b

As commented you left out the evaluation of the expression. Also there's no reason for using printfn "%A", since we know the return type.

Sum(5,10)        // Expr
|> evaluate      // int
|> printfn "%i"  // unit

In your second attempt you're mixing things up a bit.

type Expr = int -> int -> int

As Lee (again) correctly points out, this signature represents a function with 2 arguments.

let add : Expr = fun a b -> a + b

Or shorthand

let add : Expr = (+)

As Expr represents an arithmetic operator, following evaluate function combines them.

let evaluate (a : Expr) (b : Expr) = 
    match a,b with
    | a,b -> (fun a -> a + b)
  • If you're intent was to sum two input integers, a and b should've been of type int.
  • If you want to combine multiple expressions, FuneSnabel's proposal is the way to go.

For example: (1 + 2) * (3 + 4)

type Expr =
    | Cte of int
    | Add of Expr * Expr
    | Mul of Expr * Expr

let rec eval = function
    | Cte(i)   -> i
    | Add(a,b) -> eval a + eval b
    | Mul(a,b) -> eval a * eval b

Mul( Add(Cte 1,Cte 2) , Add(Cte 3,Cte 4) )
|> eval
|> printfn "%i"
4
votes

Attempt 1 looks about right but limited as it doesn't allow you to create expressions like 1+2+3.

Instead you could try something like this:

type Expr =
  | Constant  of int
  | Add       of Expr*Expr
  | Sub       of Expr*Expr
  | Mul       of Expr*Expr
  | Div       of Expr*Expr

A bit more flexible and with support for bindings like x, y and so on:

type Expr =
  | Constant  of int
  | Binding   of string
  | BinaryOp  of string*(int -> int -> int)*Expr*Expr
  | UnaryOp   of string*(int -> int)*Expr