OCaml中的API设计理念

时间:2013-07-10 11:14:52

标签: functional-programming ocaml

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

关于using list or array,但关于API设计。

例如,如果我要为用户撰写stack,我应该以{{1​​}}还是functional方式展示?

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

与此同时,我觉得(last_element, new_stack)方式在功能编程中更自然。

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


修改

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

stack

好的,内部标准的lib使用type 'a t = { mutable c : 'a list } 这是不可变的,但是将它封装在一个可变的记录中。

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

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

2 个答案:

答案 0 :(得分:4)

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

答案 1 :(得分:1)

在功能上(即没有可变性),您可以使用head / tail而不是pop来定义它与List完全相同,或者您可以按照建议让API通过返回元组来处理状态更改。这与州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) []

如果你可以制作这个操作符,你就重新发明了状态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) []

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