功能上解决问题:如何使用Haskell?

dor*_*mon 2 recursion haskell functional-programming

我试图解决H99中的一个问题:将列表分成两部分; 给出了第一部分的长度.

不要使用任何预定义的谓词.

例:

> (split '(a b c d e f g h i k) 3)
( (A B C) (D E F G H I K))
Run Code Online (Sandbox Code Playgroud)

我可以快速找到解决方案:

split'::[a]->Int->Int->[a]->[[a]]
split' [] _ _ _     = []
split' (x:xs) y z w = if y == z then [w,xs] else split' xs y (z+1) (w++[x])

split::[a]->Int->[[a]]
split x y = split' x y 0 []
Run Code Online (Sandbox Code Playgroud)

我的问题是我正在做的只是以递归格式重写循环版本.这是你在Haskell做事的正确方法吗?这不是命令式编程吗?

编辑:另外,你如何在这里避免额外的功能?

Dan*_*ons 6

通常可以将命令式解决方案转换为Haskell,但是你是对的,你通常希望找到更自然的递归语句.特别是对于这一点,基础案例和归纳案例的推理可能非常有用.那么你的基本案例是什么?为什么,当拆分位置为0时:

split x 0 = ([], x)
Run Code Online (Sandbox Code Playgroud)

通过将列表的第一个元素添加到使用n-1分割的结果上,可以构建归纳案例:

split (x:xs) n = (x:left, right)
  where (left, right) = split xs (n-1)
Run Code Online (Sandbox Code Playgroud)

这可能不会表现得非常好(它可能没有您想象的那么糟糕),但它说明了我第一次遇到问题并希望在功能上接近它时的思考过程.

编辑:另一个更依赖Prelude的解决方案可能是:

split l n = (take n l, drop n l)
Run Code Online (Sandbox Code Playgroud)

  • @jozefg是的,但是当谈到向新手解释事物时,你经常不得不对错误概念(通常来自之前暴露于严格的函数语言,如ML或Scheme)进行斗争,尾递归必然会使用常量空间,非尾递归必然使用线性空间.正如你和我所知,这些陈述在Haskell中通常都不正确.但是,我经常看到新手向后弯曲,编写一个尾部递归版本的东西,最好写成`foldr`-我最喜欢的例子之一是`find`. (3认同)
  • 我很好奇Platform/GHC有什么,它是`(采取nl,drop nl)`解决方案.Dunno究竟是为什么,但值得一提的是,"take"和"drop"的平台/ GHC版本都有列表融合优化,所以重点是元组的两个元素都是"好的生产者",这样它们就可以融入到与消费者相同的循环.(这是为什么Haskell初学者不应该被推入Haskell尾部递归的另一个原因 - 由于懒惰和融合优化,我们经常有"尾递归坏,非尾递归好"的情况...... (2认同)