【问题标题】:haskell equivalent for loop with if statements带有 if 语句的 haskell 等效 for 循环
【发布时间】:2018-08-06 15:01:12
【问题描述】:

你好菜鸟问题,但我正在学习 Haskell

我试图了解如何迭代 0 到 n 并在迭代包含 5 时返回 false,否则返回 true

这是我的方法

 Fivecount :: Integral a => a -> Bool

 isfivecount z =  False

在 JAVA 中想要这样的东西

method boolean (n)
{
for(int i = 2; i < n; i++){
  if(i == 5){
    return true;
  }
  else{
    return false;
 }
}

在此先感谢朋友 :) 解释也很酷,只是真的想了解 Haskell。

【问题讨论】:

  • 请注意,您的 Java 代码与您的英文解释相矛盾。
  • 请注意,通过翻译 Java 来学习 Haskell 是最糟糕的方法之一,通常会产生非惯用代码。一般来说,这适用于任何语言:通过翻译 Haskell 来学习 Java 也会导致奇怪的、非惯用的代码。我建议改为阅读 Haskell 教程(例如 LYAH)。

标签: function haskell boolean iteration


【解决方案1】:

已经有一些很好的答案,并且您已经接受了一个,但请允许我更详细地说明为什么某些不同版本等同于 for 循环。 (警告:一堆未经测试的代码。)请记住,通常不会从命令式方法开始并转换它来编写函数式程序。您将使用不同的策略,例如尾递归。事实上,严格的尾递归可以做任何for 循环可以做的事情。

您提供的当前代码示例似乎与您的规范不匹配,因为它总是返回false。听起来好像一个简单的return (i &gt;= 5) 就可以做到,但由于这是一个学习示例,让我们编写一个for 循环:

public static boolean containsFive( int n )
{
  for ( int i = 0; i <= n; i++ )
    if ( i == 5 )
      return true;

  return false;
}

for 循环与while 循环相同,如下所示:

public static boolean containsFive( int n )
{
  int i = 0;

  while ( i <= n ) {
    if ( i == 5 )
      return true;

    i++;
  }

  return false;
}

保持简单,我们可以做的一件事是将do 循环转换为函数调用,以i 作为参数:

public static boolean containsFive(int n)
{
  return containsFive( n, 0 );
}

private static boolean containsFive( int n, int i )
{
  if (i > n)
    return false;
  else if (i == 5)
    return true;
  else
    return containsFive( n, i+1 );
}

循环的每次迭代现在都替换为尾递归调用。该解决方案与 Haskell 的翻译很接近:

containsFive :: Integral a => a -> Bool
containsFive n = containsFive' 0 where
  containsFive' i =
    if      i > n  then false
    else if i == 5 then true
    else                containsFive' (i+1)

但是,您更有可能看到使用模式保护编写此代码:

containsFive :: Integral a => a -> Bool
containsFive n = containsFive' 0 where
  containsFive' i | i > n     = false
                  | i == 5    = true
                  | otherwise = containsFive' (i+1)

一个细微的区别是 Java 将严格评估表达式,例如 n-1 或 i+1,而 Haskell 默认情况下会延迟评估它们。但是,编译器可能会判断出使参数严格更优化,如果不是,有几种不同的方式来判断它。我们也可以传入n 作为第二个参数,就像我们在Java 中所做的那样,但是由于我们这里的辅助函数是嵌套的,n 仍然在作用域内,不需要再次传递它。

另一个是Integral 类型类更像interface,它涵盖了一些比Int 更大、更复杂的类。 Integer 更像是一个bignum。而且,如果您想知道,您可以指定containsFive' :: Integral a =&gt; a -&gt; a -&gt; Bool,但编译器可以从表达式i &gt; n 推断出i 必须与n 具有相同的类型,这是某种Integral。 == 运算符来自 Eq 类型类,&gt; 来自 Ord 类型类。 Haskell 中的数字常量是根据fromInteger 函数定义的,每种Num 都实现了该函数,因此在上下文中,常量0、1 和5 将自动转换为i 的类型,不管那是什么。 + 运算符也来自 Num。由于我们指定该类型实现Integral,它还必须实现Num、Eq 和Ord,所以我们没问题。

所以让我们手动处理所有这些,并假设每个递归调用只是将 i 的值传递到寄存器中,就像在 Java 中一样。

首先,代码运行递归函数,i 最初设置为 0。代码生成器已经可以优化为每个函数调用创建堆栈帧的开销,并且每次都重复使用同一个——我们调用的是完全相同的函数,所以它具有相同数量的相同参数类型和需要相同大小的堆栈。不需要改变n的值,但需要将包含i的寄存器设置为i+1。然后,它跳回到函数的开头并再次运行它,直到循环条件失败或测试i == 5 通过并提前终止。这与我们原始循环的操作完全相同! (好吧,除非它被积极优化。)

通过计数向下,您更有可能看到这一简化,如下所示:

containsFive :: Integral a => a -> Bool
containsFive 5 = true
containsFive n | n > 0     = containsFive (n-1)
               | otherwise = false

你也可以在 Java 中倒计时,写 for ( int i = n; i &gt; 0; i-- ) 和我交谈过的一些 C 程序员发誓。

【讨论】:

    【解决方案2】:

    通常,如果您尝试遍历列表(在本例中为列表[0..n]),您正在谈论折叠。在这种情况下,折叠在函数之上:

    \n acc -> n /= 5 && acc
    

    或者,简单地说

    (&&) . (/=5)
    

    所以你的函数变成:

    isFiveCount n = foldr ((&&) . (/=5)) True [0..n]
    

    或无积分

    isFiveCount = foldr ((&&) . (/=5)) True . enumFromTo 0
    

    要使用显式递归将其写出,您只需不断增加您的值,直到达到n 或5。

    isFiveCount n = go 0
      where
      go 5 = False  -- base case FAIL
      go x | x == n    = True  -- base case SUCCESS
           | otherwise = go (x+1)
    

    当然,通过简单的逻辑,您可以将这一切简化为

    isFiveCount n = n < 5
    

    【讨论】:

      【解决方案3】:

      如果您愿意使用库函数,一种可能性是使用any 编写如下内容:

      anyFivesInRangeZeroTo :: (Eq a, Num a, Enum a) => a -> Bool
      anyFivesInRangeZeroTo n = not $ any (5 ==) [0..n]
      

      如果您更喜欢类似循环的方法,另一种选择是使用递归:

      anyFivesInRange' :: (Enum t, Ord t, Num t) => t -> Bool
      anyFivesInRangeZeroTo' nn = 
        let
          iter 5 _ = False
          iter n nn = if n < nn then iter (succ n) nn else True
        in 
          iter 0 nn
      

      这相当于一个 for 循环,其索引从零开始并递增 1,直到找到 5(通过辅助函数的第一个参数上的模式匹配)并返回 False 或到达循环边界并返回True.

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-04-12
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多