简体   繁体   English

函数式编程:如何处理函数式编程中的异常或其等价物

[英]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.我有一个类似于上述用 Java 编写的逻辑,并希望将其迁移到 Haskell/Clojure 中的功能代码。 How to handle this in the callers of divide ?如何在divide的调用者中处理这个?

I know that the above code is totally imperative .我知道上面的代码是完全必要的 It was not written with the forethought of migrating it to FP in future.它的编写并没有考虑将来将其迁移到 FP。

Kindly advise me with a sample code in Haskell or Clojure .请用HaskellClojure 中的示例代码为我提供建议。

The following shows how you could do it in Haskell.下面展示了如何在 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 .根据上式siginure divide :: Int -> Int -> Either [Char] Int你可以看到,功能divide将返回一个Left stringRight Int

Either is an algebraic data structure and there are many more and you can write your own to. Either代数数据结构,还有更多,您可以编写自己的数据结构

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你可以在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.在 Haskell 中,您可以使用error "and your error message"抛出错误,但这会使您的程序崩溃……这不是我们想要的。

The divide function is not total: some values from its input domain have no image. divide函数不是全数:其输入域中的某些值没有图像。

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.在像 Clojure 这样的动态类型语言中,您可以返回nil ,但任何其他值也可以工作,只要您能将它与数字区分开来。 In a statically typed language like Haskell, use Data.Either or your own datatype if you need.在像 Haskell 这样的静态类型语言中,如果需要,可以使用Data.Either或您自己的数据类型。

  • The check is done consistently and statically in Haskell.该检查在 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.但是,您也可以拥有一个包装函数must-divide ,它会在出现错误时引发异常。

  • 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.在 Clojure 中,您可能会忘记检查nil ,这可能是由于错误或因为您比编译器拥有更多关于除数的信息。 You could however force a consistent checking by exporting a divide macro that requires you to consider the error path:但是,您可以通过导出需要您考虑错误路径的divide宏来强制进行一致检查:

     (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).此外,您可能希望您的大部分操作使用有效输入调用divide ,并且不想在每次调用时检查结果是否有效(例如,某些数学方程带有证明除数不可能永远为空)。 In that case, Clojure and Haskell support exceptions.在这种情况下,Clojure 和 Haskell 支持异常。 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:在 Clojure 中,这与 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.异常也是 Clojure 的一部分,因为它采用了 JVM 的执行模型。

In Clojure(script), you can use the Failjure library which provides a way to handle exceptions in a functionally pure way.在 Clojure(script) 中,您可以使用Failjure库,它提供了一种以纯函数方式处理异常的方法。

(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. (f/fail ..)将返回一个 Failure 对象,您可以将其用于错误处理。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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