为什么为负参数定义`take`和`drop`?

时间:2015-07-27 08:12:13

标签: haskell

Prelude显示带有否定参数的takedrop的示例:

take (-1) [1,2] == []
drop (-1) [1,2] == [1,2]

为什么这些定义方式如下: x !! (-1)做“更安全”的事情并崩溃了吗?即使在论证没有意义的情况下,这似乎是一种使用这些函数的hackish和非Haskell类似的方法。这背后是否有一些我没有看到的更大的设计理念?这种行为是否由标准保证,或者这就是GHC决定如何实施它?

3 个答案:

答案 0 :(得分:13)

主要有一个很好的理由使take成为局部:它可以保证结果列表(如果有的话)始终具有所请求的元素数。

现在,take已经在另一个方向上违反了这一点:当您尝试获取的元素多于列表中的元素时,只需要一样多,即更少比要求。也许不是最优雅的事情,但在实践中,这往往会非常有用。

take的主要不变量与drop结合使用:

take n xs ++ drop n xs  ≡  xs

即使n为负,也是如此。

不检查列表长度的一个好理由是它使函数在惰性无限列表上表现良好:例如,

take hugeNum [1..] ++ 0 : drop hugeNum [1..]

会立即将1作为第一个结果元素。如果takedrop首先必须检查输入中是否有足够的元素,那么这是不可能的。

答案 1 :(得分:6)

我认为这是设计选择的问题。

当前定义确保属性

take x list ++ drop x list == list

适用于任何x,包括负数以及大于length list的那些。

但我可以看到take / drop变体中的值出错:有时崩溃比错误的结果更好。

答案 2 :(得分:4)

  

x !! (-1)更安全"事情和崩溃

崩溃不安全。使功能不完全会破坏你的能力 基于其类型来推断函数的行为。

让我们想象一下,takedrop确实发生过'#34;崩溃的消极"行为。考虑他们的类型:

take, drop :: Int -> [a] -> [a]

这种类型的一件事肯定并没有告诉你这个功能可能会崩溃!如果我们使用的是完整的语言,即使我们不是 - 一个名为 fast and loose reasoning 的想法,这对推理代码很有帮助 - 但是为了能够做到这一点,你必须尽可能避免使用(和编写)非总函数。

然后,如何处理可能失败或没有结果的操作?类型就是答案! <{1}}的真正安全的变体将具有模拟失败案例的类型,例如:

(!!)

这比safeIndex :: [a] -> Int -> Maybe a

的类型更可取
(!!)

通过简单的观察,可以没有(总)居民 - 你不能发明&#34;如果列表为空,则为(!!) :: [a] -> Int -> a

最后,让我们回到atake。虽然他们的类型并没有完全说出行为是什么,再加上他们的名字(理想情况下是一些QuickCheck属性),我们得到了一个非常好的主意。正如其他响应者所指出的,这种行为在许多情况下都是适当的。如果真正需要拒绝负长度输入,则不必在非整体性(崩溃)或出现意外行为的可能性(接受负长度)之间做出选择 - 建模可能的负责任的结果类型。

这种类型清楚表明没有结果&#34; 对于一些输入:

drop

更好的是,这种类型使用自然数; 这种类型的功能甚至不可能 适用于负数!

takePos, dropPos :: Int -> [a] -> Maybe [a]