假设我们想在ReaderT [(a,b)]
monad上使用Maybe
,然后我们想在列表中进行查找。
现在,一个简单而且不太常见的方法是:
第一种可能性
find a = ReaderT (lookup a)
然而,对于ReaderT变压器的工作方式来说,似乎确实存在一些非常重要的事情。查看Control.Monad.Reader的源代码,很明显这很好用。但我没有阅读任何支持此文件的文件。但是我们也可以写这样的发现:
第二种可能性
find a = do y <- ask
lift (lookup a y)
类似的想法适用于包裹MaybeT
,StateT
,State
和Reader
。通常我会写第一个例子,但大部分时间都很明显如何像第二个例子那样编写它,你甚至可以说它更具可读性。所以我的问题是:如果第一个例子的代码被认为是坏的吗?
答案 0 :(得分:9)
我认为第一种方式的最大问题是:
如果mtl作者(或您使用的任何变换器库)决定停止导出ReaderT的数据构造函数,那么它将停止工作。这发生在从mtl 1到mtl 2的版本颠簸中的State monad并且它非常烦人。然而,
ask
是Reader的官方api的一部分,你应该计划坚持下去。
另一方面,我不会认为第一种方法是错误的。
答案 1 :(得分:3)
至少存在速度差异。
我写了一个program,它使用随机gen作为状态,并且在运行时必须生成大约5000000个随机值。现在考虑这两个函数,它们掷骰子:
random16 = State $ randomR (1,6) -- Using the internal representation
random16' = do
s <- get
(r,s') <- randomR (1,6) s
put s'
return r
第一个,程序在大约6秒钟内运行,而第二个程序慢得多,大约需要8秒钟。我可以想象,它对于读者来说是类似的,所以当运行时很重要时,也许使用这个而不是更清晰。我使用了严格的版本。
答案 2 :(得分:3)
mtl 库的当前版本 - 基于转换器库 - 在使用简单{时,为此目的导出函数reader :: (r -> a) -> Reader r a
{1}} monad。所以我们看到库的设计确实考虑了这种用法。由于没有为Reader
提供此类功能,我们可以肯定地说,使用ReaderT
正式支持的方法是直接使用构造函数。
如果你说类似的ReaderT
应该添加到库中,我会同意你的意见。这对于一致性和允许有一天改变内部表示而不破坏任何人的代码的可能性都是好的。
但就目前而言,你的“第一种可能性”是要走的路。