我正在编写一个基于lwt的Twitter API库,我想使用Lwt_stream库实现Twitter API的cursor功能。
我决定使用Lwt_stream.from
并为此函数提供参数f
。
这是我目前的工作
let get_follower ~access_token ~screen_name ?(count=5000) ?(wait=true) () =
let base_uri = "https://api.twitter.com/1.1/followers/ids.json" in
let cursor = ref (-1) in
let f () =
Client.do_get_request
~uri_parameters:
["screen_name", screen_name;
"cursor", (string_of_int (!cursor));
"count", (string_of_int count)]
~uri:(Uri.of_string base_uri)
~access_token:access_token
() >>= fun res ->
match res with
| Ok (header, str) -> begin
match (Yojson.Safe.from_string str |> user_ids_of_yojson) with
| `Ok uids ->
cursor := uids.next_cursor;
return (Some (Ok uids))
| `Error msg -> failwith msg
end
| Error e -> return (Some (Error (process_error_exn e))) in
Lwt_stream.from f
我不确定是否应该使用ref
。 我使用ref
的原因是f
的行为取决于它之前返回的值。具体而言,将使用cursor
的值下次取决于当前next_cursor
,如果cursor
为零,f
知道它到达终点并返回None
。
在这里使用ref
被认为是一个很好的设计选择吗?有没有更好的方法来实现此功能?
答案 0 :(得分:2)
Antron对我的观点给出了很好的答案,但我想分享一些实用的建议。如果我是你,我不会使用一个名为f
的大函数和ref
,好吧,主要是因为它闻起来,而且它是不可读的并且不能扩展。我将使用Lwt_stream.create
创建一个流,并使用递归函数来处理它,它执行主循环,所有逻辑在辅助函数中分开。主循环函数可以具有重复的状态,因此我们不需要丑陋的引用或显式可变性。
因此,下面是一个示例,说明如何重构代码。我也没有找到零游标的显式检查,应该用来停止流(正如你在文中提到的那样),所以我添加了这个。否则代码应该像你的一样工作。
所以,我把它分成三个函数:make_request
负责发出请求。 parse_response
是一种纯粹的转变,它解构了答案。最后,loop
函数执行主循环,发出请求,解析它们并在零上停止。
注意:看起来您正在使用Or_error
monad并将其与异常混合使用。这是一个坏主意,因为它打破了前提条件,返回Or_error.t
的格式良好的函数永远不会抛出异常。
let get_follower ~access_token ~screen_name ?(count=5000) ?(wait=true) () =
let base_uri = "https://api.twitter.com/1.1/followers/ids.json" in
let make_request cursor =
Client.do_get_request
~uri:(Uri.of_string base_uri) ~access_token
~uri_parameters:[
"screen_name", screen_name;
"cursor", string_of_int cursor;
"count", string_of_int count
] () in
let parse_response = function
| Error e -> Error (process_error_exn e)
| Ok (header, str) ->
match Yojson.Safe.from_string str |> user_ids_of_yojson with
| `Ok uids -> Ok uids
| `Error msg -> failwith msg in
let stream,push = Lwt_stream.create () in
let rec loop cursor =
make_request cursor >|= parse_response >>= function
| Ok {next_cursor=0} -> push None; return_unit
| Ok {next_cursor=n} as r -> push (Some r); loop n
| error -> push (Some error); loop cursor in
async (fun () -> loop (-1));
stream
但是,当然这个实现将急切地从服务器中提取数据,并将其推向下游。它就像一个泵和管道系统。一旦调用了功能,泵就会打开,它会不断地将水倒入系统。你可以使用有限推动来点击,可以使用Lwt_stream.create_bounded
获得。但是你仍然会有一些预取(或者回到我们的管道系统类比,你会有一些扩展坦克)。通常,这并不错,因为它消除了一些延迟,但有时这不是你想要的。在这种情况下,剩下的唯一选择是使用显式引用单元来控制循环(并使用Lwt_stream.from
构建流)。其他方法是改变界面,也许你试图将很多东西打包成一个抽象。也许返回follower list Lwt.t
甚至follower Lwt.t list
会更好。或者您甚至可以创建一个隐藏线程并返回follower
的抽象类型follower list
,并将其访问者提升到_ Lwt.t
,例如
module Follower : sig
type t
val name : t -> string Lwt.t
...
end
在这种情况下,此接口可以与Lwt_list
模块完全兼容。
答案 1 :(得分:1)
由于f
需要unit
并且每次产生不同的结果,正如您所说,它必须依赖于某些状态,正如我认为您所知道的那样。它已经通过依赖I / O的结果来做到这一点,我认为这就是使ref
非平凡的问题得到回答的问题。否则,答案是肯定的,这是必要的(见下面的第(2)点)。
我认为消除句法ref
有两种主要的可能性,但它们都没有意义。
f
内部以某种方式隐藏这些状态位,即在API上的对等端上。这看起来似乎不太可能,事实上它似乎必须在客户端上才能使API工作。ref
相同的精神。您可以编写或使用某种类型的包装器来解决这个问题ref
,但除非该包装器在许多地方都有用,否则只会使得在此处存在额外的客户端状态变得不那么明显。我宁愿将州保持为本地,尽可能接近其使用。由于f
已经必然会做“脏”的事情,f
是正确的地方。因此,总而言之,我会说保持ref
不变。无论如何,流通常是有状态的。
上述讨论假定您必须使用Lwt_stream
。如果没有,您可以提供一个替代接口,其类型看起来像get_follower : cursor:int -> ... -> (results * cursor) Lwt.t
,如果必须,让调用者担心状态。这可能是我首先尝试的。
修改
当然有一个缺点,即编写调用代码以提供错误的光标的可能性。您可以通过返回部分应用的函数来隐藏光标,但可能会调用调用代码来调用错误的函数。如果你担心这些可能性,没有线性类型,流方法会更安全,因此它有其优点。