转载

如何利用多级抽象思维来设计库?

编者按:之前,我们曾发表《 函数式语言库模式:框架是魔鬼? 》该文论述了库与框架之间的区别,及如何设计组合化的库。而本文作者在此之前,还发表了一篇《 Library patterns: Multiple levels of abstraction 》,结合具体实例,向大家非常详细地介绍了库设计模式及库设计中的多级抽象思想。

以下为具体译文:

库设计模式

对于库设计理论来说,有几点在我的实际工作过程中体会最深:

  • 迭代设计—— 首先,不要为了一个库而设计库。在F#中,你可以把多个功能都放入一个脚本然后按需进行引用或复制至其它项目。这是最好的库需求分析途径。一旦你想出更好的点子,就可以把它以文件形式加入到一个新项目中;
  • 可组合的—— 可组合性是函数式编程的关键理论,其重要性等同于库设计。一个库应当可以让用户以简单的方式来进行二次开发,实现更多更复杂的功能;
  • 避免回调—— 回调是很容易影响可组合性的。当你编写一些复杂的函数时(例如处理Markdown文档),你可能会受到诱惑而进行参数化回调(例如置入一个预处理器来对文档进行解析和翻译间的转换)。回调的问题是会使你的代码被加上太多的结构。这不但不会带来灵活性,反而随着回调的增多而使设计变得更加复杂;
  • 抽象级别—— 那么我们怎么才能找到简单易用的API并在不同场合进行使用?问题的关键是能提供多级抽象。

我认为上述几点是最能影响库设计好坏的。本文将先就抽象级别一点展开论述。

库是如何被使用的?

每个库都对应着一定的典型应用场合。比方说, F# Formatting格式工具可以对一个目录下的所有文 如何利用多级抽象思维来设计库? 件进行文档生成,这占到使用频率的80%。有时我们可能需要以不同的方式来处理个别文件(例如使用不同模板)。一个完整的库应能兼顾该需求。还有某些时候我们需要以别的方式来处理某个文件,如添加一个自动生成内容表(TOC)。

对于类似的情况,我找到一种行之有效的处理方法—以多级功能抽象的方式来创建库。在最高级,单一个函数调用应处理 80%的应用场合。然后如果有需要,你可以再多建一级来处理额外15%的应用场合。最后如果还有需要,就再多建一级来处理最后4%的应用场合。对于最后的1%,我的建议是发送一个pull request!

该设计模式在核心功能库中是被深度采用的,例如 F#链表库。遵循该模式,还能使我们的库成为领域的特定语言从而更具可读性。

示例 #1:链表

用链表来阐述多级抽象是很有代表性的。

  • 高级:高频函数

在高级抽象,可以使用高频函数来对链表进行处理 (或在C#中使用LINQ)。例如从0到100的整数中获取正的sin值链表,可以这样编写:

如何利用多级抽象思维来设计库?

类似 List.map和List.filter的高频函数在链接处理中有80%频率会用到甚至更高。但有时我们可能需要非高频操作。

  • 低级:递归和模式匹配

例如根据符号变更对链表进行分拆,例如把 [1; 4; -3; 2]分为[1; 4]和[-3; 2]。如果不借助低频API以递归模式匹配进行处理,单靠高频函数是很难实现的:

如何利用多级抽象思维来设计库?

loop中,进行了三种处理:

  1. 全部元素都是同符号;
  2. 发现有一个符号发生变更;
  3. 在符号变更前已经遍历所有元素。

如果是使用 C#中的IEnumerable<T>,或许操作起来略显复杂,但是仍有一些低级API可供使用(使用GetEnumerator进行临时集合和转化)。

  • 从低级转到高级

集合 API设计的好处是可以实现从低级到高级的转换。splitAtSignChange函数在根据邻近函数值进行拆分时,可以看成是一个更综合操作的一个实例:

如何利用多级抽象思维来设计库?

该函数与前个版本十分相似—不同的是增加了额外的参数 f来判定什么时候断开链表。虽然函数本身使用了低级API,但提供了转为高级的途径。

如何利用多级抽象思维来设计库?

再回头看高级的定义是能包含更简单的编程方式。也就是说,根据 X轴的临界值来对从1到10的sin值进行链表进行拆分:

如何利用多级抽象思维来设计库?

可见这就实现了到高级的转换—使用两个简单明了的函数处理拆分问题。

示例 #2:3D的领域特定语言

这是另一个典型的例子,特别是在进行自定义对象建模时。

  • 超高级:创建城堡

其实现代码如下:

如何利用多级抽象思维来设计库?

如何利用多级抽象思维来设计库?

在高级抽象层面,我们仅仅使用 4行代码就把城堡创建好了!但这仅能进行非常受限制的创作(规矩的城墙和塔组成的城堡),或许我们想做得更多。

  • 高级:3D对象组合
如果想使用不同的塔外形该如何处理呢?要查看低级 3D渲染代码吗?不必。tower函数本身就是根据另一种语言或抽象等级来进行编写的。一个塔就是一个填色后的圆柱体加上一个填色的有正确朝向的圆锥体:

如何利用多级抽象思维来设计库?

使用库时,一开始是高级抽象的,但熟悉之后,我们可以进入下一级。在这级中,我们可以打造自己的语言 (抽象)例如前述的List.splitAt。

  • 低级:使用OpenGL进行面部渲染
在进行 3D渲染时存在一个低级操作。以该库为例,调用OpenGL原始操作是低级的(使用OpenTK包装),这稍微有点复杂:

如何利用多级抽象思维来设计库?

如果想以更简单的方式来生成图形,我们可以在 3D原始操作和渲染之间加多一层,但这不是我们要讨论的。即便如此,我们还是可以看出多级抽象的重要性。最明显的一点是可以从已创建的事物中发现如何实现高级到低级的转换(例如塔是如何组成的),然后以其它方式使用低级原始操作。

换言之,如果你直接以调用 OpenGL的方式来生成塔,那么是很难再生成其它形态塔的。但如果有多级抽象机制,这就不是问题了。

综述

本文主要论述了库设计中的多级抽象思想。在你的库中,应该提供高级函数来帮助用户处理 80%的任务。对于剩下的15%,应该提供一个低级API。关键的一点是高级API可根据低级API来呈现。这样不但满足了日常需求,还给用户留有二次开发的空间,是更加吸引和友好的。

(编译/伍昆 责编/张红月)

英文来自: Tomas Petricek's blog

正文到此结束
Loading...