【问题标题】:The API design philosophy in OCamlOCaml 中的 API 设计理念
【发布时间】:2013-07-10 21:09:56
【问题描述】:

在学习 OCaml 半年之后,我仍然在 functional programmingimperative programming 位上苦苦挣扎。

不是关于using list or array,而是关于API设计。

例如,如果我要为用户写stack,我应该以functionalimperative 的方式呈现它吗?

stack 应该有一个名为pop 的函数,这意味着将最后一个元素返回给用户并将其从堆栈中删除。所以如果我以functional 的方式设计我的stack,那么对于pop,我应该返回一个元组(last_element, new_stack),对吗?但我认为它很丑。

同时我觉得functional的方式在函数式编程中更自然。

那么,我应该如何处理这种设计问题呢?


编辑

我看到stack的源代码,他们定义的类型是这样的:

type 'a t = { mutable c : 'a list }

好的,标准库内部使用不可变的list,但将其封装在可变记录中。

我的理解是这样的,对于用户来说,它总是一个堆栈,因此不需要一个元组返回给客户端。

但是,它仍然不是一种功能性的方式,对吧?

【问题讨论】:

  • 也许你可以让pop返回对,top返回元素,drop返回子栈。

标签: functional-programming ocaml


【解决方案1】:

可变结构有时效率更高,但它们不是持久的,这在各种情况下都很有用(主要用于回溯失败的计算)。如果不可变接口与可变接口相比没有或几乎没有性能开销,那么您绝对应该更喜欢不可变接口。

【讨论】:

  • 但是在我的问题中,对于函数pop,它必须返回一个元组,这看起来不错吗?
  • 我认为返回一个元组很好。
【解决方案2】:

在功能上(即没有可变性),您可以通过使用 head/tail 而不是 pop 将其完全定义为 List,或者您可以按照您的建议让 API 通过返回一个元组来处理状态更改。这类似于 state monad 的构建方式。

因此,要么由父作用域负责处理堆栈的状态(例如通过递归),在这种情况下堆栈与列表完全相同,要么通过元组将部分责任加载到 API。

这是一个快速的尝试(假装知道 O'Caml 语法):

module Stack =
  struct
    type 'a stack = 'a list
    let empty _ = ((), [])
    let push x stack = ((), x::stack)
    let pop (x::stack) = (x, stack)
      | pop _ = raise EmptyStack
  end

一个用例是:

let (_, st) = Stack.empty ()
let (_, st) = Stack.push 1 Stack.empty
let (_, st) = Stack.push 2 st
let (_, st) = Stack.push 3 st
let (x, st) = Stack.pop st

您可能希望始终隐藏st 的传递,而不是显式处理元组,并发明一个使以下语法成为可能的运算符:

let (x, st) = (Stack.empty >>= Stack.push 1 >>=
               Stack.push 2 >>= Stack.push 3 >>= Stack.pop) []

如果你能做这个操作符,你就重新发明了 state monad。 :)

(因为上面的所有函数都将状态作为其最后一个参数,所以它们可以部分应用。为了对此进行扩展,所以发生了什么更明显,但可读性较差,请参见下面的重写。)

let (x, st) = (fun st -> Stack.empty st >>= fun st -> Stack.push 1 st
                                        >>= fun st -> Stack.push 2 st
                                        >>= fun st -> Stack.push 3 st
                                        >>= fun st -> Stack.pop) []

这是处理状态和不可变数据结构的一种惯用方式,至少。

【讨论】:

  • 感谢您的回答。但一个问题是我应该像你说的那样做吗?我的意思是这是向用户客户端展示堆栈的好方法/最佳方法吗?
  • 在设计 API 时,我想您应该看看它的构建对象。如果期望调用这个 Stack 模块的所有地方都被命令式地编写,那么一元代码将会突出。作为功​​能风格的倡导者,我仍然更喜欢一致性。
  • 社区 wiki 意味着其他人可以编辑我的帖子。万一我犯了错误,我希望其他人可以轻松修复它。
  • 我猜在你的建议中,只有pop 才需要元组,对吧?对于push empty 等,我们只需要返回新的堆栈
  • 是的,push 只会返回一个堆栈而不是一个元组。我通常将push 留给不可变类型,而改用add。比如setmap
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-01-19
  • 1970-01-01
  • 1970-01-01
  • 2013-05-21
  • 2012-01-27
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多