如果您想对源级函数执行此操作,例如:
myFoo x y = x + y
除非您想在编译器中四处乱窜,否则您几乎不走运。但是,您可以使用一些合适的注释定义自己的支持此功能的函数概念。让我们将此概念称为 a UserAction a
,其中a
是操作的返回类型。为了在 中组合计算UserAction
,它应该是Monad
. 不用想太多,我的第一印象是使用这堆 monad 转换器:
type UserAction = WriterT [LogEntry] (ReaderT FuncIdentifier IO)
该WriterT [LogEntry]
组件表示 aUserAction
运行时会生成一个 s [1] 序列LogEntry
,其中包含您要写入数据库的信息;就像是:
data LogEntry = Call FuncIdentifier FuncIdentifier
现在可以推迟存储随机种子、任务标识符等——可以通过将信息添加到LogEntry
.
该ReaderT FuncIdentifier
组件说 aUserAction
取决于 a FuncIdentifier
; 即,调用它的函数的标识符。
FuncIdentifier
可以通过简单的东西来实现
type FuncIdentifier = String
或者,如果您愿意,可以使用具有更多结构的东西。
该IO
组件表示UserAction
s 可以对文件、控制台、生成线程等进行任意输入和输出。如果您的操作不需要它,请不要使用它(Identity
改为使用)。但是由于您提到生成随机数,我认为您并没有考虑纯粹的计算[2]。
然后,您将使用以下函数注释要记录日志的每个操作:
userAction :: FuncIdentifier -> UserAction a -> UserAction a
这将像这样使用:
randRange :: (Integer, Integer) -> UserAction Integer
randRange (low,hi) = userAction "randRange" $ do
-- implementation
userAction
将记录通话并设置其被叫者记录他们的通话;例如:
userAction func action = do
caller <- ask
-- record the current call
tell [Call caller func]
-- Call the body of this action, passing the current identifier as its caller.
local (const func) action
从顶层运行所需的操作,完成后,收集所有LogEntry
s 并将它们写入数据库。
如果您需要在代码执行时实时编写调用,则需要一个不同的UserAction
monad;但您仍然可以呈现相同的界面。
这种方法使用了一些中间的 Haskell 概念,例如 monad 转换器。我建议在 IRC 上irc.freenode.net
#haskell
寻求有关填写此实施草图细节的指导。他们是一群善良的人,很乐意帮助您学习:-)。
[1] 在实践中,您不想使用[LogEntry]
而是DList LogEntry
为了性能。但是更改很容易,我建议您继续使用,[LogEntry]
直到您对 Haskell 更加熟悉,然后切换到DList
.
[2] 随机数生成可以纯粹完成,但它需要进一步的大脑重新布线,这个草图已经有很多了,所以我建议把它当作一个IO
效果来开始。