新定义工具函数是否应设为偏函数?无限列表cycle实现抉择咨询
这个问题在Haskell社区里一直有讨论,并没有绝对统一的共识,但有几个主流的倾向和权衡维度可以帮你做决策:
1. 传统base库的偏函数设计背景
base库中的cycle、head、(!!)等偏函数,是历史遗留与实用优先的产物。早期Haskell更注重代码的简洁性和表达力,默认假设调用者会自行保证输入的合法性(比如调用cycle时不会传入空列表),这样写出来的代码更紧凑直观。但这种设计的代价是,一旦输入不合法就会触发运行时异常,调试起来比较棘手。
2. 现代社区的安全优先趋势
近年来,Haskell社区越来越强调避免不必要的偏函数,尤其是在新开发的库或者面向新手的场景中。比如专门的safe包就提供了所有base偏函数的安全替代版本(如safeHead :: [a] -> Maybe a),通过Maybe或Either明确暴露可能的失败场景。
对于你的InfList模块来说,实现cycle :: [a] -> Maybe (InfList a)在语义上其实更准确:空列表根本无法生成有效的无限循环序列,用Maybe来表达“可能成功、可能失败”的状态,完全符合Haskell“让非法状态不可表示”的设计理念,也能避免调用者不小心踩坑。
3. 决策时的关键权衡点
你可以根据自己的模块定位和使用场景来选择:
- 模块定位:如果是内部团队使用,大家都清楚调用约定,或者需要和base库的风格严格对齐,那偏函数版本可能更方便;如果是对外发布的通用库,或者面向对偏函数陷阱不熟悉的开发者,安全版本显然更友好。
- 语义准确性:
InfList是无限列表,cycle空列表确实没有合理的返回值,用Maybe明确这个边界情况,比默默抛出异常更符合函数的语义。 - 可用性兼容:如果不想放弃偏函数的便捷性,可以同时提供两个版本:比如保留偏函数
cycle :: [a] -> InfList a(一定要在文档里醒目标注“输入不能为空”),同时提供safeCycle :: [a] -> Maybe (InfList a)的安全版本,让用户根据需求自行选择。
4. 社区最佳实践参考
现在很多主流的Haskell库(比如text、vector)都会优先提供安全操作函数,或者至少在文档中明确标注偏函数的风险。另外GHC也在逐步增加对偏函数的编译警告(比如-Wpartial-fields、-Wincomplete-patterns等),这也能看出社区对代码安全性的重视正在提升。
总的来说,没有绝对的对错,但如果是新开发的模块,尤其是通用型模块,优先实现带Maybe的安全版本是更稳妥的选择;如果需要兼容base的使用习惯,双版本并存也是很常见的做法。
内容的提问来源于stack exchange,提问作者schuelermine

