【问题标题】:Organising data and code across modules in Prolog在 Prolog 中跨模块组织数据和代码
【发布时间】:2016-07-20 20:55:47
【问题描述】:

我正在开发一个简单的 Web 服务,它将用户提供的事实添加到我的 Prolog 数据库中(使用 assert)。我认为最好将这些动态事实(“数据”)与我对这些事实(“代码”)进行操作的服务规则分开,因此将它们分成两个不同的模块。主要原因是我想定期将动态事实保存到磁盘,同时能够开发没有问题且独立于用户数据的代码。 我一直在使用assert(my_store:fact(...)) 将用户数据添加到my_store 模块,然后在代码模块中我开始编写规则,例如

:- module (my_code, [a_rule/1, ...]).

a_rule(Term) :-
   my_store:fact(...), ...

看起来一切正常,但使用这种方法my_store 被硬编码在代码模块中,这有点令人担忧。例如,如果一段时间后我决定更改数据模块名称,或者我可能需要两个独立的数据模块,一个具有频繁持久性,另一个仅偶尔进行持久性,该怎么办?

谁能建议代码和数据组织的最佳实践是什么?也许代码和数据的分离违反“Prolog 方式”?有没有深入探讨这些问题的好书?

干杯, 杰克

【问题讨论】:

    标签: module prolog


    【解决方案1】:

    这是一个很好的问题,涉及到几个非常重要的话题。

    我希望以下 cmets 可以帮助您解决大部分问题,如果您跟进您最感兴趣的点,并提出单独解决特定问题的新问题,可能会更有效。

    1. 首先,当接受用户代码作为输入时,请确保您只允许将安全代码添加到您的程序中。在 SWI-Prolog 中,有safe_goal/1,它可以帮助您确保一些安全属性。它并不完美,但总比没有好。

    2. 同样在 SWI-Prolog 中,有 library(persistency)。请仔细研究文档以了解其工作原理,以及如何使用它在磁盘上存储动态数据。

    3. 关于模块名称,我有两个 cmets:

      • 显式 模块限定的目标非常少见。只需加载模块并使用它的子句。
      • 请注意,您可以动态地加载模块。也就是说,没有什么可以阻止您将use_module/1 与从其他地方获得的参数一起使用。这使您可以更灵活地指定要从中获取定义的模块,而无需更改和重新编译代码。

    关于代码和数据的分离:在Prolog中,没有这样的分离。

    所有 Prolog 子句都是 Prolog 术语。

    条款!条款一直下降!

    【讨论】:

    • 感谢提示。 (1) 服务不应该接受原始代码,而是根据用户发送的请求创建事实。然而,很高兴知道safe_goal,我不知道;将来可能有用。 (2) 我偶然发现了persistency 图书馆,还没来得及查,但它在我的阅读清单上。 (3) 我不确定我是否遵循该建议。如果事实是由用户 HTTP 请求动态创建并存储在数据模块中,我如何加载该模块?每次运行代码时都需要刷新它吗?或者指令use_module(my_store) 可以发挥所有作用?
    • 一路向下?那是乌龟,我知道。
    • @Jacek:您使用use_module/1 加载模块,例如:update_from(Module) :- use_module(Module).,其中update_from/1 可以在Module 可用后调用。或者,只需让 HTTP 服务器运行 make/0 以在新定义可用时更新所有定义。如果您想了解有关此特定方面的更多信息,请提出一个单独的问题。评论太长了。
    • 仅仅因为代码和数据的区别在solution域中不存在,并不意味着它在problem 域作为(可能)关键概念,可能需要尽可能准确地表示。
    • @mat:use_module 的问题在于它对文件进行操作,而我的模块是在用户数据到达时动态创建的。我将在一分钟内提出一个解决方案。
    【解决方案2】:

    感谢@mat 的建议,让我阅读和思考更多。现在,我可以针对我的问题发布一个潜在的解决方案;不理想,不使用persistency 库,而是简单的第一次尝试。

    如前所述,用户数据存储在assert(my_store:fact(...)) 中。这意味着模块my_store 是动态创建的,并且没有允许使用use_module 的文件。但是,我可以使用 import/1 谓词来导入动态断言的事实,因此我的解决方案如下所示:

    :- module(my_code, [a_rule/1, ...]).
    
    :- initialization import_my_store.
    
    import_my_store :-
       import(my_store:fact/1),
       import(my_store:another_fact/1),
       ...
    
    a_rule(Term) :-
       fact(...), ...
    

    请注意,我可以在不明确指定 my_store 模块的情况下使用 fact/1。而且我还可以轻松地将用户数据转储到文件中。

    save_db(File) :-
       tell(File),
       my_store:listing,
       told.
    

    缺点是在初始化时import/1 调用会生成警告,例如:import/1: my_store:fact/1 is not exported (still imported into my_code)。但这不是什么大问题,因为它们仍然被导入到 my_code,我可以在没有明确的模块规范的情况下使用用户事实。

    期待听到任何 cmets。干杯,

    【讨论】:

    • 为什么不简单地使用include/1 来包含文件中的所有定义?
    • 因为最初没有代表my_store 模块的文件。如果可能的话,我想保持这种状态,因为这样可以让我在可能拥有的数据模块数量上有更多的余地。
    • 可以简单测试文件是否存在,只有文件存在才使用include/1( exists_file(F) -> include(F) ; true)
    • 好的,但是如果还没有模块文件,用户添加了一些数据并且我想在没有明确的模块规范的情况下使用这些数据,会有什么帮助?
    • include/1 使不同文件的子句就像它们出现在同一个文件中一样可用,因此您不需要模块规范。请注意,此类结构的使用类似于逻辑hacking,而不是逻辑编程。我仍然建议使用library(persistency),或者您自己的模块文件和use_module/1。模块导出的谓词都是可用的没有显式模块前缀!还要查看multifile/1 指令:它似乎适用于您的用例。
    【解决方案3】:

    使用 Logtalk 的解决方案,它提供了模块的替代方案。首先用你的代码定义一个对象:

    :- object(my_code).
    
        :- public([a_rule/1, ...]).
    
        :- private([fact/1, another_fact/1, ...]).
        :- dynamic([fact/1, another_fact/1, ...]).
    
        a_rule(Term) :-
            ::fact(...), ...
    
        ...
    
    :- end_object.
    

    然后,根据需要动态创建任意数量的数据存储,作为my_code 对象的扩展(派生原型):

    ?- create_object(my_store, [extends(my_code)], [], []).
    

    要查询数据存储,只需向其发送消息:

    ?- my_store::a_rule(Term).
    

    create_object/4 内置谓词可以在必要时为存储加载持久性文件(以便您从离开的地方继续):

    ?- create_object(my_store, [extends(my_code)], [include('my_store.pl'))], []).
    

    用户数据可以通过按预期断言保存在数据存储中:

    ?- my_store::assertz(fact(...)).
    

    您需要一个谓词将数据存储转储到文件中,作为my_code 对象中的公共谓词。例如:

    :- public(dump/0).
    dump :-
        self(Self),
        atom_concat(Self, '.pl', File),
        tell(File),
        dump_dynamic_predicates,
        told.
    
    dump_dynamic_predicates :-
        current_predicate(Functor/Arity),
        functor(Template, Functor, Arity),
        predicate_property(Template, (dynamic)),
        ::Template,
        write_canonical(Template), write('.\n'),
        fail.
    dump_dynamic_predicates.
    

    现在您可以转储数据存储,方法是输入:

    ?- my_store::dump.
    

    请注意,使用此解决方案可以轻松同时拥有任意数量的数据存储。如果数据存储需要代码的专用版本,那么您可以简单地扩展代码对象,然后创建专用数据存储作为该专用对象的扩展。

    【讨论】:

    • 感谢 Paulo 提供替代解决方案。目前,我更多地使用 SWI-Prolog,但我会牢记 Logtalk 方法。
    • SWI-Prolog 是受支持的 Prolog 系统之一(您也可以将上述解决方案与其他 11 个 Prolog 系统一起使用)。请注意,这是一个您可以使用 withfrom SWI-Prolog 的解决方案,而不是 either SWI-Prolog 或 Logtalk 解决方案方案。
    猜你喜欢
    • 2018-01-08
    • 2011-01-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-08-21
    • 1970-01-01
    • 2013-09-24
    • 1970-01-01
    相关资源
    最近更新 更多