将函数包装到 monad 转换器中是否/应该被视为不好的做法?
假设我们想要在 Maybe
monad 上使用 ReaderT [(a,b)]
,然后我们想要在列表中进行查找。
现在,一个简单且不太罕见的方法是:
第一种可能性
find a = ReaderT (lookup a)
然而,这似乎确实断言了有关 ReaderT 转换器如何工作的一些不平凡的事情。查看 Control.Monad.Reader 的源代码,很明显这工作得很好。但我还没有阅读任何支持这一点的文档。然而,我们也可以这样写 find:
第二种可能性
find a = do y <- ask
lift (lookup a y)
类似的想法也适用于包装 MaybeT
、StateT
、State
和阅读器
。通常我会写类似第一个示例的内容,但大多数时候,如何像第二个示例一样编写它是非常明显的,您甚至可能会说它更具可读性。所以我的问题是:像第一个示例这样的代码应该被认为是不好的吗?
Let's say we want to use ReaderT [(a,b)]
over the Maybe
monad, and then we want to do a lookup in the list.
Now an easy, and not too uncommon way to this is:
first possibility
find a = ReaderT (lookup a)
However it does seem like this asserts some non-trivial thing about how the ReaderT transformer works. Looking at the source code for Control.Monad.Reader it's clear that this works just fine. But I haven't read any documentation supporting this. However we could also write find like this:
second possibility
find a = do y <- ask
lift (lookup a y)
Similar ideas hold for wrapping MaybeT
, StateT
, State
and Reader
. Usually I write something like the first example, but most of the time it is really obvious how to write it like the second example, and you might even say it's more readable. So my question is: should code like the first example be considered bad?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
我认为第一种方式最大的问题是:
另一方面,我不认为第一种方法是错误的。
I think the biggest problem with the first way is:
On the other hand, I wouldn't consider the first way wrong.
mtl 库的当前版本 - 基于 transformers 库 - 导出函数
reader :: (r -> a) ->当使用简单的
正是为了这个目的。所以我们看到库的设计确实考虑到了这种用法。由于Reader
monad 时,Reader r aReaderT
没有提供这样的函数,因此我们可以有信心地说,官方支持的使用ReaderT
执行此操作的方法是直接使用构造函数。如果你说类似的
readerT :: Monad m => ,我会同意你的观点。 (r→a)→ ReaderTM a
应该添加到库中。这对于一致性和允许有一天在不破坏任何人的代码的情况下更改内部表示的可能性都是有好处的。但就目前而言,“第一种可能性”是要走的路。
The current version of the mtl library - which is based on the transformers library - exports the function
reader :: (r -> a) -> Reader r a
for exactly this purpose when using the simpleReader
monad. So we see that the design of the library does take into account this usage. Since no such function is provided forReaderT
, we can say with some confidence that the officially supported way to do this withReaderT
is to use the constructor directly.I'll agree with you if you say that an analogous
readerT :: Monad m => (r -> a) -> ReaderT r m a
ought to be added to the library. That would be good both for consistency, and for allowing the possibility of changing the internal representation someday without breaking anyone's code.But for now, your "first possibility" is the way to go.
至少速度上有差距。
我写了一个程序,它使用随机生成作为状态,并且在运行时必须生成大约5000000个随机值。现在考虑这两个掷骰子的函数:
第一个函数的程序运行时间约为 6 秒,而第二个函数则慢得多,大约需要 8 秒。我可以想象,它对于读者来说是相似的,所以当运行时很重要时,也许可以使用这个而不是更清晰的。我为此使用了严格版本。
At least there is a speed difference.
I wrote a program, which uses a random gen as a state and must generate about 5000000 random values while running. Now consider these two functions, which roll a dice:
Whith the first one, the program runs in about 6 seconds, while the second one is much slower, taking about 8 seconds. I can image, that it is similar for reader, so maybe use this one instead of the more clearer when runtime is important. I used the strict version for this.