【问题标题】:Functional Programming: How to handle exceptions in Functional Programming or what is its equivalent函数式编程:如何处理函数式编程中的异常或其等价物
【发布时间】:2018-06-15 03:29:14
【问题描述】:

假设,我有以下代码。

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

如何在函数式编程中编写这个?

我有一个类似于上面用 Java 编写的逻辑,并希望将其迁移到 Haskell/Clojure 中的功能代码。 在divide的调用者中如何处理?

我知道上面的代码是完全命令式的。编写它并没有考虑到将来将其迁移到 FP。

请用 HaskellClojure 中的示例代码告诉我。

【问题讨论】:

  • 在 FP 中,您有代数数据结构,在这种情况下可以使用的数据结构是 Either。
  • @Roman:如果可以的话,请用示例代码解释一下。
  • 上面的代码如何“完全命令式”?它不是total,但它没有中间状态。您可以在 Haskell 中编写相同的代码,包括异常等等……您是否也认为这是必要的?
  • 如果你认为异常是bottom的化身,那么上面的divide方法在我看来referentially transparent;即它是function

标签: java haskell design-patterns clojure functional-programming


【解决方案1】:

下面展示了你如何在 Haskell 中做到这一点。

根据类型 siginure divide :: Int -> Int -> Either [Char] Int,您可以看到函数 divide 将返回 Left stringRight Int

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"

你可以在repl.it上玩它

在 Haskell 中,您可以向 error "and your error message" 抛出错误,但这会使您的程序崩溃。这不是我们想要的。

【讨论】:

  • 我认为maybe monad 更适合这个,实际上不,你是对的,如果你想传递一个错误消息,这会很好:D
  • @Netwave 我也想过选择Maybe,但后来我因为错误消息而选择了Either:P
  • 就如何在 Haskell 中处理此类事情而言,您的答案是完全正确的,但作为一个小问题,OP 的代码并没有拒绝否定 - 他试图防止不正确的结果如果您将可能的最小整数除以 -1,就会发生这种情况。
【解决方案2】:

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

【讨论】:

    【解决方案3】:

    divide 函数不完整:来自其输入域的一些值没有图像。

    使函数总计

    更改输出域,使其可以返回错误或数字。 调用者负责检查该值是真的数字还是错误。

    在像 Clojure 这样的动态类型语言中,您可以返回 nil,但任何其他值也可以使用,只要您能将它与数字区分开来。 在像 Haskell 这样的静态类型语言中,如果需要,请使用 Data.Either 或您自己的数据类型。

    • 检查是在 Haskell 中一致且静态地完成的。即使您确定除数不能为空,您也必须每次都进行检查。但是,您也可以有一个包装函数 must-divide,然后会在错误时引发异常。

    • 在 Clojure 中,您可能会忘记检查 nil,这可能是由于错误或您对除数的了解比编译器更多。但是,您可以通过导出需要您考虑错误路径的 divide 宏来强制进行一致检查:

      (divide x y :on-error (throw ...))
      (divide x y :on-error default-value)
      

      ... 可以分别展开为:

      (or (maybe-divide x y) (throw ...))
      (or (maybe-divide x y) default-value)
      

      ...与

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

    抛出异常

    数学运算被组合成更大的表达式:在其中添加显式错误处理路径很快就会变得不可读。 此外,您可能希望您的大部分操作都使用有效输入调用divide,并且不想在每次调用它时检查结果是否有效(例如,一些数学方程带有除数不会的证明可能永远为空)。在这种情况下,Clojure 和 Haskell 支持异常。这使您可以在调用堆栈中捕获更高层的错误,以防出现错误。

    【讨论】:

      【解决方案4】:

      在 Clojure 中,这与 Java 并没有什么不同:

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

      您的代码没有改变任何变量,因此已经非常实用。异常也是 Clojure 的一部分,因为它采用了 JVM 的执行模型。

      【讨论】:

      • 请注意,这是一个 partial 函数的示例,它没有为所有输入定义,因为它会引发异常。 (与 total 函数形成对比,如 Roman's Haskell example 所示,它为任何输入返回一些值。)
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-05-01
      • 2010-09-06
      • 2017-10-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多