注意:我试图在不使用库函数的情况下实现这一点
一般来说,这不是一个好主意。一个更好的练习是这样的:
- 弄清楚如何使用库函数来实现它。
- 弄清楚如何自行实现您在第 1 步中使用的任何库函数。
通过这种方式,您将学习三项关键技能:
- 什么是标准库函数,以及它们何时有用的示例。
- 如何将问题分解成更小的部分
- 如何编写库中的基本函数。
但是,在这种情况下,您的 whatIndex 与 elemIndex in Data.List 或多或少是相同的函数,因此您的问题归结为编写您自己的此库函数版本。
这里的诀窍是你想在向下递归列表时增加一个计数器。编写尾递归函数有一种标准技术,称为累加参数。它的工作原理是这样的:
- 您编写了一个 辅助 函数,与“前端”函数相比,该函数需要一个(或更多)额外参数来跟踪额外信息。
- 然后将“真实”函数定义为对辅助函数的调用。
所以对于elemIndex,辅助函数应该是这样的(i作为当前元素索引的累加参数):
-- I'll leave the blanks for you to fill.
elemIndex' i x [] = ...
elemIndex' i x (x':xs) = ...
那么“驱动”函数是这样的:
elemIndex x xs = elemIndex 0 x xs
但是这里我必须提到一个严重的问题:让这个函数在 Haskell 中表现良好是很棘手的。尾递归在 strict(非惰性)函数式语言中是一个有用的技巧,但在 Haskell 中则不然,因为:
- Haskell 中的尾递归函数仍然可以爆栈,
- 非尾递归函数可以在恒定空间中运行。
This older answer of mine 显示了第二点的示例。
因此,在您的情况下,非尾递归解决方案可能是您可以提供的最简单的解决方案,它将在恒定空间中运行(即,不会在长列表中破坏堆栈):
elemIndex x xs = elemIndex' x (zip xs [0..])
elemIndex' x pairs = snd (find (\(x', i) -> x == x') pairs)
-- | Combine two lists by pairing together their first elements, their second
-- elements, etc., until one of the lists runs out.
--
-- EXERCISE: write this function on your own!
zip :: [a] -> [b] -> [(a, b)]
zip xs ys = ...
-- | Return the first element x of xs such that pred x == True. Returns Nothing if
-- there isn't one, Just x if there is one.
--
-- EXERCISE: write this function on your own!
find :: (a -> Bool) -> [a] -> Maybe a
find pred xs = ...