在Haskell中实现循环

时间:2013-11-04 10:45:08

标签: haskell

我很想知道在Haskell中实现一个循环。我怎样才能在Haskell(伪代码)中做类似的事情:

var i = 0
for (int i1 = 0; i1 < 10; i1++) {
  println(i1)
  i += 2
}

println(i)

5 个答案:

答案 0 :(得分:8)

在功能方面,你正在做的是折叠整数列表,以便为每个整数打印元素并将累加器增加2.因为我们正在打印某些东西(即做I / O),我们需要{{ 3}}但是它只是你的标准fold in a monad

foldM (\i i1 -> print i1 >> return (i + 2)) 0 [0..9] >>= print

我们使用lambda函数折叠,该函数使用与代码相同的变量名称。即i1是当前元素,i是累加器。

下一个参数是累加器的初始值,对应于代码中的i = 0

最后一个参数是(包括在两端)我们折叠的数字列表。

>>=(绑定运算符)将折叠结果(即累加器i的最终值)传递给打印函数。

编辑:这是假设您打算写

var i = 0
for (int i1 = 0; i1 < 10; i1++) {
  println(i1)
  i += 2
}

println(i)

而不是在for-clause和循环体中仅递增i

答案 1 :(得分:6)

遵循两个假设

  1. “类似”是指行为相似,
  2. 你想要打印
  3. (同样的假设)

    0
    1
    2
    3
    4
    5
    6
    7
    8
    9
    20
    
  4. 我会在Haskell中写

    do
      let xs = [0..9]
      mapM_ print xs
      print (length xs * 2)
    

    您可以看到原始计算如何分成三个独立的(并且是独立的!)计算。

    1. 我们将循环变量i1转换为列表。我们知道边界是0和9,所以循环变量可以用列表[0..9]表示。
    2. 我们打印列表的内容,这与每次迭代打印循环变量的内容相同。
    3. 我们根据我们对列表的了解来计算“i”,然后将其打印出来。
    4. 第三个计算特别有趣,因为它强调了传统命令式编程和Haskell之间的区别,Haskell与声明性编程有很多关系。

      在列表的每次迭代中将两个加到数字上与获取列表的长度并将其乘以2相同。在我看来,最大的区别在于,我可以阅读i = length xs * 2并了解它在眨眼之间意味着什么。然而,在循环的每次迭代中计算i,需要一些思考来理解它的真正含义。

      所有三个子计算都是独立的这一事实意味着它们更容易测试 - 您可以一次测试一个,如果它们单独工作,它们也可以一起工作!

      如果您在“类似代码”的意义上表示“相似”,请参阅STRef / IORef个答案中的任何一个。

答案 2 :(得分:2)

您可以使用像forM_这样的高阶函数。该名称看起来有点奇怪,但它实际上非常系统:该函数称为for,它适用于monad(M)并且它不返回值(_):{ {1}}。

您可以像这样使用它:

forM_

import Control.Monad import Data.IORef main = do i <- newIORef 0 forM_ [0..9] $ \ i' -> do print i' i `modifyIORef` (+ 2) readIORef i >>= print 函数本身可以通过递归实现。这是一个简单的方法:

forM_

当然,在Haskell中使用这样的可变状态是很难看的。不是因为它丑陋,而是因为它很少被使用。你可以想象一个看起来更好的C类图书馆;看看this example

当然,在Haskell中执行此操作的最佳方法是采用更具功能性的方法并忘记可变变量。你可以写下这样的东西:

forM_ []     _ = return ()
forM_ (x:xs) f = f x >> forM_ xs f

我们还可以使用一些非常简单的函数定义来改进循环代码。只是拥有一个很好的操作员来阅读,设置和修改main = do forM_ [0..9] print print $ sum [i' * 2 | i' <- [0..9]] 有很长的路要走:

IOVar

这让我们可以这样编写循环:

(!) :: (a -> IO b) -> IORef a -> IO b
f ! var = readIORef var >>= f

(+=) :: Num a => IORef a -> a -> IO ()
var += n = var `modifyIORef` (+ n) 

ref :: a -> IO (IORef a)
ref = newIORef

此时,它几乎看起来像OCaml!仅仅为几个运营商定义提供了相当大的改进。

答案 3 :(得分:1)

import Data.IORef
import Control.Monad

main = do
   i <- newIORef 0

   let loop = do
        iNow <- readIORef i
        when (iNow < 10) $ do
          print i
          modifyIORef i (+1)
          loop
   loop

但显然,你应该避免这种情况。 mapM_ print [0..9]效果更好。


我看到你必须有i个不同增量的变量。嗯,很明显如何在这里添加。您可以通过循环保持手动递归。更好的是用简单的IORef替换一个forM_。最好是尽量不使用任何IORef,而只使用功能结构。

答案 4 :(得分:1)

一种简单而灵活的循环方法是在let或where子句中定义递归函数:

main = loop 0 10
  where
    loop i n | i < n = putStrLn (show i) >> loop (i+2) n
    loop _ _         = return ()

这定义了两个变量loopi的函数n。第一个模式有一个 guard (在|之后),它检查条件(i < n)。如果确实如此,则选择此分支并在i再次调用自身之前将loop打印到控制台,现在将i绑定到i+2。否则,选择默认分支,仅returns ()(=在IO Monad中不执行任何操作)。

使用递归函数实现这样的循环非常灵活。如果很容易,你想要使用更高阶函数(例如formap),但如果你无法弄清楚如何从命令式设置转换某些循环,那么递归函数通常可以做到这一点。