我正在创建一个系统,需要存储用户在数据库中运行的所有功能和参数。没有记录被删除,但我需要能够重新创建确定性再生的最小功能序列和参数集。
用户交互非常少,它们不是编程 - 在C ++中处理输入交互作为数据传递到FFI作为数据累积到列表和回调来处理当前数据缓冲区。该函数触发一系列决定,如何连接数据库中的数据集的处理图,以及它们输入的函数。该图是非循环的。最初运行此图表,并为用户显示值。图的后面部分将重新组合以生成新图。
这些图的Haskell内部构造是通过分析数据库中的数据和组合之间的简单随机选择而创建的。我希望能够存储一个随机生成器的种子,它适用的模块和参数id。
我认为这可能最好将EDSL的功能存储在数据库中,其中只存储高级别的交互但是完全确定。
我对存储值不感兴趣,而是存储操作的功能图。
每个表都指不同的功能。每条记录都有一个日期和一个任务ID,可以将特定操作的所有功能分组。参数引用表ID和记录ID。如果组合函数在内部执行类似生成随机数的操作,则应自动存储该数字的种子。
我正在使用没有GHCI和持久性SQlite的GHC第1阶段。
我还是Haskell的新手,我正在寻找哪种方法和软件包适合以功能方式解决这个问题。
答案 0 :(得分:3)
如果要对源级功能执行此操作,例如:
myFoo x y = x + y
除非你想在编译器中进行黑客攻击,否则你几乎没有运气。但是,您可以使用一些合适的注释来定义自己的功能概念,以支持此功能。我们将此概念称为UserAction a
,其中a
是操作的返回类型。为了在UserAction
中撰写计算,它应该是Monad
。没想太难,我的第一印象就是使用这堆monad变换器:
type UserAction = WriterT [LogEntry] (ReaderT FuncIdentifier IO)
WriterT [LogEntry]
组件表示UserAction
在运行时会产生一系列LogEntry
s [1],其中包含您要写入数据库的信息;类似的东西:
data LogEntry = Call FuncIdentifier FuncIdentifier
现在推迟存储随机种子,任务标识符等是可以的 - 可以通过向LogEntry
添加信息将其合并到此设计中。
ReaderT FuncIdentifier
组件表示UserAction
取决于FuncIdentifier
;即,正在调用它的函数的标识符。
FuncIdentifier
可以通过简单的
type FuncIdentifier = String
如果你愿意,可以使用结构更多的东西。
IO
组件表示UserAction
可以对文件,控制台,生成线程,整个批次进行任意输入和输出。如果您的操作不需要,请不要使用它(请改用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
并将其写入数据库。
如果您需要在代码执行时实时编写调用,则需要使用不同的UserAction
monad;但你仍然可以呈现相同的界面。
这种方法使用一些中间Haskell概念,如monad变换器。我建议将IRC转到irc.freenode.net
#haskell
频道,以获取有关填写此实施草图详细信息的指导。他们很善良,很乐意帮助你学习: - )。
[1]在实践中,您不希望使用[LogEntry]
而是DList LogEntry
来表现。但是改变很容易,我建议你使用[LogEntry]
,直到你对Haskell更加熟悉,然后切换到DList
。
[2]随机数生成可以纯粹完成,但它需要进一步的大脑重新布线,这个草图已经有很多,所以我建议将其视为IO
效果,以便开始。