简体   繁体   中英

Functional Programming: How to handle exceptions in Functional Programming or what is its equivalent

Let's say, I've the below code.

public int divide(int dividend, int divisor) {
    if( divisor == 0 || (dividend == Integer.MIN_VALUE && divisor == -1)) 
          throw new DivisionException();
    return dividend/divisor;
}

How to write this in Functional Programming?

I have a logic similar to the above written in Java and would like to migrate that to functional code in Haskell/Clojure. How to handle this in the callers of divide ?

I know that the above code is totally imperative . It was not written with the forethought of migrating it to FP in future.

Kindly advise me with a sample code in Haskell or Clojure .

The following shows how you could do it in Haskell.

Based on the type siginure divide :: Int -> Int -> Either [Char] Int you can see that the function divide will return either a Left string or a Right Int .

Either is an algebraic data structure and there are many more and you can write your own to.

divide :: Int -> Int -> Either [Char] Int
divide dividend divisor
    | (divisor == 0) = Left "Sorry, 0 is not allowed :o"
    | (dividend == (minBound :: Int)) && (divisor == -1) = Left "somethig went wrong"
    | otherwise = Right (dividend `div` divisor)

main = do
    print (divide 4 2)                     -- Right 2
    print (divide 4 0)                     -- Left "Sorry, 0 is not allowed :o"
    print (divide (minBound :: Int) (-1))  -- Left "somethig went wrong"

You can play with it on repl.it

In Haskell you can throw errors to with error "and your error message" but this will crash you programm.. and this is not what we want.

The divide function is not total: some values from its input domain have no image.

Make the function total

Change the output domain so that it can return either an error or a number. The caller is responsible for checking whether the value is really a number, or an error.

In a dynamically typed language like Clojure, you could return nil , but any other value could work too, as long as you can distinguish it from a number. In a statically typed language like Haskell, use Data.Either or your own datatype if you need.

  • The check is done consistently and statically in Haskell. You must do the check every time, even if you are sure the divisor can't be null. However, you can also have a wrapper function, must-divide , which then would throws an exception on errors.

  • In Clojure, you may forget to check for nil , either from a bug or because you have more information about the divisor than the compiler. You could however force a consistent checking by exporting a divide macro that requires you to consider the error path:

     (divide xy :on-error (throw ...)) (divide xy :on-error default-value)

    ... could be respectively expanded as:

     (or (maybe-divide xy) (throw ...)) (or (maybe-divide xy) default-value)

    ... with

    (defn maybe-divide [dividend divisor] (and (not (zero? divisor)) (or (not= Integer/MIN_VALUE dividend) (not= -1 divisor)) (/ dividend divisor)))

Throw an exception

Mathematical operations are composed to form bigger expressions: adding an explicit error handling path inside them can quickly become unreadable. Also, you may expect most of your operations to call divide with valid inputs, and don't want to check if the result is valid each time you call it (eg some mathematical equation come with a proof that the divisor won't possibly ever be null). In that case, Clojure and Haskell support exceptions. This allows you to catch errors higher up in the call stack in case you have bugs.

In Clojure this isn't really different from Java:

(defn divide
  [dividend divisor]
  (if (or (zero? divisor)
          (and (= Integer/MIN_VALUE
                  dividend)
               (= -1 divisor)))
    (throw (DivisionException.))
    (/ dividend divisor)))

Your code isn't mutating any variables and therefore is already pretty much functional. Exceptions are just as much a part of Clojure, because it adopts the execution model of the JVM.

In Clojure(script), you can use the Failjure library which provides a way to handle exceptions in a functionally pure way.

(ns stackoverflow
    (:require
      [failjure.core :as f]))

(defn divide
  [dividend divisor]
  (cond
    (zero? divisor) (f/fail "The divisor is 0; unable to perform operation.")
    (and (= Integer/MIN_VALUE dividend) (neg? divisor)) (f/fail "Unable to perform division with a negative dividend and divisor")
    :else (/ dividend divisor)))

(f/fail ..) will return a Failure object which you can use for your error handling.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM