Haskell嵌套循环累加器

时间:2012-09-10 15:28:00

标签: haskell loops functional-programming accumulator

我正在努力将我的服务器端编程语言从PHP修改为Haskell,因此我并不完全熟悉函数式语言设计。

这里我想将以下php函数的部分处理代码转换为Haskell:

function loop($A) {
  $array1 = array();
  $num = (int)$A;
  if ($num == 0)
    $array1 = "0";
  for (int $i=0; $i<$num; $i++)
    foreach (loop($i-$num) as &$value)
      $array1[] = "+ ".$value." ";  //concat
  return $array1;
}

我想会有大量使用map / mapM_函数,但积累如何工作?我开始认为增加的安全性在这一点上不值得改造。

感谢。

2 个答案:

答案 0 :(得分:6)

这是我正在回答的时候的PHP:

function loop($A) {
  $array1 = array();
  $num = (int)$A;
  if ($num == 0)
    $array1 = "0";
  for (int $i=0; $i<$num; $i++)
    foreach (loop($i-$num) as &$value)
      $array1[] = "+ ".$value." ";  //concat
  return $array1;
}

上述代码到Haskell的字面翻译将是:

loop :: Int -> [String]
loop 0 = ["0"]
loop num = ["+ " ++ value ++ " " | i <- [0..num], value <- loop (i - num)]

它仍然没有实现任何目标,但它确实给你带来了味道 Haskell在列表推导中的“循环”或迭代。

首先要注意的是类型签名loop :: Int -> [String] 提醒我写loop 0 = ["0"]而不是loop 0 = "0",这更像是字面上的 你有什么,但Haskell发现循环的输出如果它不一致 有时是一个字符串列表,有时只是一个字符串。我认为这是一种错误 检查它值得学习 - 也许修改时编写的所有代码都会在php中编译, 但由于设计不一致,Haskell 不会 - 它会迫使你在写作时清楚地思考 并在编译时修复bug。

我将解释我的代码,不是作为Haskell的完整解释,而是作为一个品尝者并将其与您的代码相关联:

loop :: Int -> [String]

loop是一个函数,它接受一个Int eger并返回一个字符串列表。 Haskell现在不允许您将此与任何其他数据类型一起使用,这意味着它的类型安全,并且您受到保护 来自各种令人讨厌的虫子直接开箱即用。类型系统实际上非常灵活,富有表现力 而且功能强大,是我们喜欢Haskell的原因之一,但是我在这里使用它来锁定功能。

(列表在Haskell而不是数组中广泛使用。它们非常方便,并且可以快速进行顺序访问。 有数组数据类型,但它们不太可爱/ Haskellish - 首先习惯列表。)

loop 0 = ["0"]

这意味着如果您使用Int loop调用0,它应该返回带有一个字符串“0”的列表。

loop num = [ something | stuff... ]

表示您可以在stuff中获取变量的值,并将其返回something,所以

loop num = [ "+ " ++ value ++ " " | stuff... ]

意味着我们将返回(Haskell的版本)"+ ".$value." "。在这里,我们使用++而不是php的.

i <- [0..num]

这意味着让i的范围从0num。它必须是Int,因为num是因为loop :: Int -> ...

value <- loop (i - num)

这意味着让value在调用loop的结果中使用数字i - numvalue必须String,因为loop :: Int -> [String]

在Haskell中,它就像递归调用一样实现为表达式重写, 所以他们在被叫的时候消失了,不要把堆栈弄得乱七八糟 除非你的计算固有地混乱了堆栈。

把它放在一起给出:

loop num = ["+ " ++ value ++ " " | i <- [0..num], value <- loop (i - num)]

如果这种符号感觉非常奇怪,你可能会更习惯于更强制的样式语法:

loop :: Int -> [String]
loop 0 = ["0"]
loop num = do
    i <- [0..num]
    value <- loop (i - num)
    return ("+ " ++ value ++ " ")

当然仍然没有做任何有用的事情,但至少它很容易跟踪。

为什么不通过http://learnyouahaskell.com/真实世界Haskell 在<{3}}处完成了解你的Haskell 这两个教程开始很简单但是深入。我想你会发现Haskell是编写少量正确代码以替换大量OK代码的好方法。想得更清楚,写得更少!

答案 1 :(得分:3)

看起来你有某种字典。字典的典型Haskell数据结构是Map,由Data.Map提供。因此,如果您的密钥为String且价值为Int s,那么您的地图的类型为Map String Int

现在,根据您的代码判断,您似乎将键和值混合在同一个数组中,因此首先我们必须将它们分成键值对。我会假装你的开始输入是一个列表而不是一个数组,虽然我在这里写的所有东西也适用于Haskell数组:

fix :: [a] -> [(a, a)]
fix [] = []
fix [_] = []
fix (k:v:xs) = (k, v):fix xs

您可以选择在关联列表中停止,在这种情况下,您可以将其转换为描述性字符串列表:

describe :: (Show k, Show v) => [(k, v)] -> [String]
describe xs = map (\(k, v) -> "Stored On: " ++ show k ++ ", item: " ++ show v) xs

或者我们可以直接打印出来:

printPairs :: (Show k, Show v) => [(k, v)] -> IO ()
printPairs xs = mapM_ putStrLn (describe xs)

现在,为了提高查询效率,您通常希望将关联列表存储为Map,因此您只需使用fromList中的Data.Map函数:

fromList :: Ord k => [(k, v)] -> Map k v

如果您想将列表从地图中取出(即打印出来),您只需使用toList

toList :: Map k v -> [(k, v)]