专栏名称: 程序人生
十年漫漫程序人生,打过各种杂,也做过让我骄傲的软件;管理过数十人的团队,还带领一班兄弟姐妹创过业,目前在硅谷一家创业公司担任 VP。关注程序人生,了解程序猿,学做程序猿,做好程序猿,让我们的程序人生精彩满满。
目录
相关文章推荐
51好读  ›  专栏  ›  程序人生

当我做 hackathon 时我在做什么 (1)

程序人生  · 公众号  · 程序员  · 2021-01-13 13:44

正文

请到「今天看啥」查看全文


pub struct ExDataFrameRef(pub RwLock);
#[derive(NifStruct)]#[module = "ExPolars.DataFrame"]pub struct ExDataFrame { pub inner: ResourceArc,}

这样一个结构,两边来回拷贝,也就几十个字节,无伤大雅,但满足了我能够很方便地在 elixir 侧进行 inspect 的需求(就好像完整的数据在 elixir 侧一样)。我只需实现 Inspect 接口即可:

defimpl Inspect, for: ExPolars.DataFrame do  alias ExPolars.Native
def inspect(data, _opts) do case Native.df_as_str(data) do {:ok, s} -> s _ -> "Cannot output dataframe" end endend

于是,当一个 dataframe 被读取出来时,在 Elixir 里,其展现是非常友好的,和 pandas 一个水准:

Well done, buddy。有什么比 elixir 和 rust 两边写了几个函数就得到了这样一个沁人心脾的结果更美妙的呢?嗯,完美的开局意味着美好的结局,我对自己说。

第一次撞墙:RAII

作为一个很懒惰的程序员,在不断遇到重复的代码 pattern 时,想到的第一件事就是 DRY (Don't Repeat Yourself)。很快,我发现在 Rust 侧要访问 dataframe,自己总要写这样的代码:

match data.inner.0.read() {  Ok(df) => deal_with_df,  Err(_) => Err(ExPolarsError.Internal(...))}

没办法,因为在 rust 侧拿到引用后,需要取读锁(或者写锁),然后才能做相应的操作。于是我想到了重构。可能是之前完美的开局让我有些飘飘然,我想也没想大手一挥,把上述代码封装到一个函数里:

def get_reader(*self) -> DataFrame {  match data.inner.0.read() {    Ok(df) => &*df,    Err(_) => Err(ExPolarsError.Internal(...))  }}

然后喜滋滋就这么一个函数一个函数地写下去,直到。。。Rust 编译器教我做人。

我才意识到读写锁返回的是一个特殊的 Result: Result , PoisonError >> ,它是 RAII 结构 [6](有关 RAII 的介绍请自行 wikipedia,否则这篇文章不用写别的了),所以我必须在同一个上下文中处理(不同的上下文意味着并发下的重入问题,rust 编译器帮我们杜绝了这种情况导致的死锁)。这是我遇到的第一个 brick wall。我的「人生导师」Randy Pausch 说:

我不想放弃我 DRY 上的努力,但封装函数此路不同,怎么办?

还好,rust 有 macros。于是,几经探索后,我写下了这段代码:

macro_rules! df_read {    ($data: ident, $df: ident, $body: block) => {        match $data.inner.0.read() {            Ok($df) => $body,            Err(_) => Err(ExPolarsError::Internal(...)),        }    };}

这就是本文开头那段示范代码中函数 df_add 里使用了奇怪的 df_read! 。这让我每个函数少些很多重复的代码,最大程度让 Rust 编译器满意,并且使我的代码足够 DRY。







请到「今天看啥」查看全文