【问题标题】:How to simplify nested-if using to return value in Haskell如何在 Haskell 中简化嵌套 if 使用以返回值
【发布时间】:2013-01-22 01:32:04
【问题描述】:

我想检查前一个if condition 的条件以确定下一个if condition 是否被执行。每个if condition 都可能返回一个值。

编辑:抱歉,我之前提供的示例看起来有点奇怪......:( 这是我的真实示例,我想为 goingToMove

简化 if-then-else
goingToMove p routes points w h = 
                        if canMove p points
                            -- the point can be moved in the map 
                            then let r = routes ++ [p]
                                     l = remainList p points
                                in move p r l w h
                            -- the point cannot be moved in the maps
                            else []

move p routes points w h = 
            if (length routes) == 2 
                then routes
                else let one = goingToMove (tallRightCorner p) routes points w h in
                    if (null one)
                        then let two = goingToMove(tallRightBCorner p) routes points w h in
                            if (null two)
                                then let three = goingToMove (tallLeftBCorner p ) routes points w h in
                                    if (null three)
                                        then ....
                                        ...... -- until, let eight = ..
                                        else three
                                else two
                        else one 

编辑:不好的例子 当这个东西用java写的时候,我可能会使用一个可变的布尔标志,并返回一个可变的数据。

public String move (int number){
        // base case
        if (number == 0){
            return "Finished the recursion";
        }
        // general case
        else {
            String result;
            boolean isNull = false;

            if ((result = move(3)) == null){
                isNull = true;
            }
            else {
                return result;
            }

            // continue to execute the if-conditions if the previous condition failed
            if (isNull){
                if((result = move(2)) == null){
                    isNull = true;
                }
                else {
                    return result;
                }
            }

            if (isNull){
                if((result = move(1)) == null){
                    isNull = true;
                }
                else {
                    return result;
                }
            }

            return null;
        }
    }

但在 Haskell 中,没有可变数据,只有if-then-else 条件。 然后代码会是这样,我想简化一下,因为在我的实际工作中,有 8 个if-then-else 的级别看起来很糟糕而且很乱......

move 0 = "Finished the recursion"
move n = 
    let one = move 3 in
    if null one
        then let two = move 2 in
            if null two
                then let three = move 1 in
                        then null
                        else three
                else two
        else one

【问题讨论】:

  • 您在 Java 代码中调用的 move 是否与您定义的 move 相同?如果是这样,我看不出它对于非零输入如何不会无限循环。 move(3) 打电话给move(3) 打电话给move(3) ...
  • 您提供的 haskell 代码也有错误的类型。您在 java 代码中的某个位置显式返回null,这意味着haskell 代码应该是Int -> Maybe String,即使它不是无限循环。哦,您建议的 haskell 代码也有语法错误(缺少包含 if 的行?),因此很难弄清楚您在做什么。
  • 我提供了一个真实的例子:(对不起
  • 你应该从使用更多的模式匹配和更少的条件开始。你还有一堆多余的lets,你不需要括号if表达式的条件。

标签: haskell if-statement recursion functional-programming tail-recursion


【解决方案1】:

没有 Maybe 的选项可能是在递归中添加一个标志(在下面的示例中,您将调用该标志设置为 1 的函数):

移动 p 个路线点 w h 1

move p routes points w h flag 
  | (length routes) == 2 = routes
  | otherwise = 
      if null result then move p routes points w h (flag+1)
      else result
        where result = case flag of 
                        1 -> goingToMove (tallRightCorner p) routes points w h
                        2 -> goingToMove (tallRightBCorner p) routes points w h
                        3 -> goingToMove (tallLeftBCorner p) routes points w h
                        --...etc.
                        _ -> []

【讨论】:

    【解决方案2】:

    如果 routes 的长度为 2 或来自 goingToMove 的一系列应用程序的第一个非空结果,则您需要 routes,这些应用程序因应用到 p 的角函数而异。

    move p routes points w h
      | length routes == 2 = routes
      | otherwise = head
                  $ filter (not . null)
                  $ map tryMove corners
        where tryMove f = goingToMove (f p) routes points w h
              corners = [ tallRightCorner
                        , tallRightBCorner
                        , tallLeftBCorner
                        -- et cetera
                        ]
    

    【讨论】:

      【解决方案3】:

      编辑:这是新示例的一些代码:

      move p routes points w h 
           | length routes == 2 = routes
           | otherwise = find (not . null) . map gtm [tallRightCorner, tallRightBCorner, tallLeftBCorner]
          where gtm f = goingToMove (f p) routes points w h
      

      请注意,这会返回一个可能。您可以使用fromMaybe 坚持默认情况。

      这是第一个提议示例中的旧(但类型检查)代码

      move 0 = "Finished the recursion"
      move n = concat . maybeToList . msum $ map move' [3,2,1]
        where move' x = let mx = move x in if null mx then Nothing else Just mx
      

      【讨论】:

      • 这不能编译(尽管问题中的代码也不能编译,所以......)
      • 哎呀,这就是我依赖我的心理类型检查器的结果。
      • 我提供了一个真实的例子:(对不起
      【解决方案4】:

      除了rampion 提供的<|> 的好用以及sclv 的类似建议之外,另一种常见的方法是使用守卫,利用惰性,

      move :: Int -> Maybe String
      move n
          | n == 0       = Just "Finished the recursion"
          | isJust move3 = move3
          | isJust move2 = move2
          | isJust move1 = move1
          | otherwise    = Nothing
            where
              move3 = move 3
              move2 = move 2
              move1 = move 1
      

      由于懒惰,move i (i = 3, 2, 1) 仅在需要时才进行评估。

      在给定的情况下,move 3 <|> move 2 <|> move 1 更好,但在条件需要评估具有不同返回类型的不同函数的情况下,在where 子句中使用保护和惰性绑定可能是避免的自然解决方案尴尬的嵌套ifs.

      【讨论】:

      • JustMaybe a 的值构造函数。由于您在 Java 中返回并检查 null,因此让 Haskell 版本返回 Maybe String 是恰当的类比。 isJust(可从Data.Maybe 模块获得)测试Maybe a 值是Just a 还是Nothing,所以isJust move3 对应于if ((result = move(3)) != null) [翻转你的if/elses]。
      【解决方案5】:

      如果我想在 Java 中执行以下操作:

      result = func1(arg);
      if (result == null){
        result = func2(arg);
        if (result == null){
          result = func3(arg);
          if (result == null){
            result = func4(arg);
          }
        }
      }
      return result;
      

      我实际上在做的是从func1(args)func2(args)func3(args)func4(args) 中找到返回非空值的第一个结果。

      在 Haskell 中,我将 func1func2func3func4 建模为返回 Maybe a 值的函数,以便它们在失败时返回 Nothing

      func1, func2, func3, func4 :: Int -> Maybe Result
      

      然后我可以使用<|> 运算符(来自Control.Applicative),它对Maybe a 具有以下定义:

      Nothing <|> x = x
      x       <|> _ = x
      

      所以我可以将上面的Java转换为

      func1 arg <|> func2 arg <|> func3 arg <|> func4 arg
      

      由于惰性求值的奇迹,func2 arg 仅在 func1 arg 返回 Nothing 时才求值,与 Java 示例中相同。

      【讨论】:

      • 看起来不错,Nothing意味着nil吗?
      • Maybe a 是一种具有两个构造函数的数据类型:Nothing :: Maybe aJust :: a -&gt; Maybe a。可以定义为data Maybe a = Nothing | Just a
      • 这很好,但你也可以使用 MonadPlus:msum [func1 arg, func2 arg],甚至msum $ map ($ arg) [func1, func2, func3, func4]。当您从其他地方获取函数列表时,这尤其有用。不过,我不太喜欢用它来定义控制流。
      猜你喜欢
      • 2019-05-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-06-24
      • 1970-01-01
      • 2020-09-28
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多