Search code examples
functiontypesf#return-type

Is there a way for a function to return either ints or strings, depending on the input?


I got this homework assignment (Couldn't add the homework tag). This is just a training problem, not part of a hand-in. I made a recursive function "eval" that evaluates common operations on integers (+, -, *, /) So far, the function can evaluate addition, multiplication and subtraction.

type expr =
    | Const of int
    | Add of expr * expr
    | Mul of expr * expr
    | Sub of expr * expr

let rec eval (n: expr): int =
    match n with
        | Const n -> n
        | Add (a, b) -> eval a + eval b
        | Mul (a, b) -> eval a * eval b
        | Sub (a, b) -> eval a - eval b

This function works perfectly. Next I have to extend the expr type, to allow for division, by adding:

| Div of expr * expr

Also I need to deal with the case of zero division, by making a new type:

('a, 'b) result

Which is the new return type of eval (This is in the assignment description, so i have to do that). This is my attempt:

type ('a, 'b) result =
    | Ok of int
    | Err of string

let rec eval (n: expr): ('a, 'b)result =
    match n with
        | Const n -> Ok(Const n)
        | Add (a, b) -> Ok(eval a + eval b)
        | Mul (a, b) -> OK(eval a * eval b)
        | Sub (a, b) -> Ok(eval a - eval b)
        | Div (_, Const 0) -> Err "zero division"
        | Div (a, b) -> Ok(eval a / eval b)

But I get an error saying that the operators +, -, * and / isn't defined for type ('a, 'b) result, which makes sense. So my question is, is there a way for a new type to inherit from other types? I can't type something like:

type ('a, 'b) result =
    | int
    | Err of string

Why is that not a thing? Do I have to specify some keyword so be associated with int, if I use int in a new type? f# wants the same output type, no matter the input, is there a way around this? Could I tell the function that the output is either an integer or a string, if I didn't create the new type?


Solution

  • Short answer is no. Like you said F# only allows one return type.

    In this case your result type has 2 possible set of values, the Ok values and the Error values. Your code needs to consider both posibilites, you cannot ignore one. Remember eval a does not return an int anymore, now it returns a result, which may or may not have an int inside it.

    After you eval a and eval b but before you call +, -, * or /, you need to check if either result is an Error. Only if both are Ok can you then apply the operator. If any of them is an Error you have to return the error, right?

    You can use match to check (and to extract the int), for instance:

    match eval  a , eval  b  with
    |     Ok    a', Ok    b' -> Ok(a' + b')
    |     Error e , _    
    |     _       , Error e  -> Error e
    

    Remember you do not need to copy the instruction above 4 times. You can create a function and pass the operator as a parameter using this syntax: (+), (-), (*), (/).

    Another tip: you are considering only the case of division by Const 0. What happens if you are dividing by a calculated 0, for instance 5/(1 - 1)?

    In Functional Programming there is another way of dealing with this. What you need in the end is a Bind function (or Apply). A good resource to learn about it is this site: https://fsharpforfunandprofit.com/rop/