13个不稳定版本 (5个破坏性版本)

0.6.0 2024年3月31日
0.6.0-alpha.12024年3月30日
0.5.0 2023年8月5日
0.4.0 2022年8月22日
0.1.3 2022年3月11日

#22 in #logic-programming

Download history 50/week @ 2024-04-14 68/week @ 2024-04-21 70/week @ 2024-04-28 35/week @ 2024-05-05 31/week @ 2024-05-12 236/week @ 2024-05-19 54/week @ 2024-05-26 125/week @ 2024-06-02 80/week @ 2024-06-09 33/week @ 2024-06-16 66/week @ 2024-06-23 69/week @ 2024-06-30 78/week @ 2024-07-07 118/week @ 2024-07-14 145/week @ 2024-07-21 159/week @ 2024-07-28

每月下载量 501次
3 个crate中使用 (通过 ascent)

MIT 许可证

205KB
4.5K SLoC

Rust中的逻辑编程

Rust Crates.io

Ascent是一种逻辑编程语言(类似于Datalog),通过宏嵌入到Rust中。

更多信息,请参阅Ascent的CC论文

此外,这篇OOPSLA论文描述了Ascent的“将自定义数据结构带入Datalog”方面。

示例

计算图中所有连接的节点

ascent! {
   relation edge(i32, i32);
   relation path(i32, i32);
   
   path(x, y) <-- edge(x, y);
   path(x, z) <-- edge(x, y), path(y, z);
}

使用Ascent

  1. 安装Rust.
  2. 创建一个新的Rust项目
    cargo new my-ascent-project
    cd my-ascent-project
    
  3. Cargo.toml中将ascent添加为依赖项
    [dependencies]
    ascent = "*"
    
  4. main.rs中编写一些Ascent代码。以下是一个完整示例
    use ascent::ascent;
    ascent! {
       relation edge(i32, i32);
       relation path(i32, i32);
       
       path(x, y) <-- edge(x, y);
       path(x, z) <-- edge(x, y), path(y, z);
    }
    
    fn main() {
       let mut prog = AscentProgram::default();
       prog.edge = vec![(1, 2), (2, 3)];
       prog.run();
       println!("path: {:?}", prog.path);
    }
    
  5. 运行程序
    cargo run
    

示例

查看ascent/examples目录,以获取更完整的示例集。

功能

Ascent支持计算用户定义的格的固定点。在Ascent中,lattice关键字定义一个格。一个lattice的最后一列的类型必须实现Lattice trait。一个lattice类似于一个关系,但当一个新的事实(v1,v2,...,v(n-1),vn)被发现,并且一个事实(v1,v2,...,v(n-1),v'n)已经在数据库中存在时,vn和v'n将通过join操作合并为一个单一的事实。

此功能使得编写在Datalog中无法表达的程序成为可能。例如,我们可以使用此功能来计算图中节点之间的最短路径长度。

ascent! {
   lattice shortest_path(i32, i32, Dual<u32>);
   relation edge(i32, i32, u32);

   shortest_path(x, y, Dual(*w)) <-- edge(x, y, w);

   shortest_path(x, z, Dual(w + l)) <-- 
      edge(x, y, w), 
      shortest_path(y, z, ?Dual(l));
}

在这个示例中,Dual<T> 是格 T 的对偶。我们使用 Dual<T> 是因为我们对最短路径感兴趣,对于任何给定的节点对,我们只存储两个路径长度 l1l2 的最小值 min(l1, l2)

并行上升

上升(Ascent)能够生成并行代码。宏 ascent_par!ascent_run_par! 可以生成并行化代码。当然,列类型必须是 Send + Sync 才能在并行 Ascent 中工作。

并行上升使用了 rayon,因此可以通过 rayonThreadPoolBuilder 或使用环境变量 RAYON_NUM_THREADS(有关更多信息,请参阅 此处)来控制并行级别。

BYODS

ascent-byods-rels on crates.io

BYODS(代表 Bring Your Own Data Structures to Datalog)是 Ascent 的一个特性,它使关系能够由自定义数据结构支持。此功能通过优化用于支持关系的数据库结构来提高 Ascent 程序的算法复杂度。例如,需要计算大型图传递闭包的程序可以通过选择基于并查集的数据结构 trrel_uf(在 ascent-byods-rels 中定义)来改进其性能,用于传递闭包关系。

Cargo.toml

[dependencies]
ascent-byods-rels = "*"
ascent = "*"
use ascent_byods_rels::trrel_uf;
ascent! {
   relation edge(Node, Node);
   
   #[ds(trrel_uf)] // Makes the relation transitive
   relation path(Node, Node);
   
   path(x, y) <-- edge(x, y);
}

属性 #[ds(trrel_uf)] 指示 Ascent 编译器使用模块 trrel_uf 中定义的数据结构提供程序来处理 path 关系。您可以在 此处 找到 BYODS 显著加快 Ascent 计算的完整示例。

有关 BYODS 的更多信息,请参阅 BYODS.MD

ascent_run!

除了 ascent! 之外,我们还提供了 ascent_run! 宏。与 ascent! 不同,当调用此宏时,它会评估 Ascent 程序。ascent_run! 的主要优点是 Ascent 程序内部具有局部变量作用域。例如,我们可以这样定义一个用于发现关系(可选自反的)传递闭包的函数

fn tc(r: Vec<(i32, i32)>, reflexive: bool) -> Vec<(i32, i32)> {
   ascent_run! {
      relation r(i32, i32) = r;
      relation tc(i32, i32);
      tc(x, y) <-- r(x, y);
      tc(x, z) <-- r(x, y), tc(y, z);
      tc(x, x), tc(y, y) <-- if reflexive, r(x, y);
   }.tc
}

在上面的示例中,我们直接初始化关系 r 以缩短程序。

我们还提供了 ascent_run_par!,这是 ascent_run! 的并行版本。

条件和生成子句

语法设计旨在让 Rust 用户感到熟悉。在这个例子中,edge 被填充了来自 node 的非自反边。请注意,任何实现了 Clone + Eq + Hash 的类型都可以用作关系列。

ascent! {
   relation node(i32, Rc<Vec<i32>>);
   relation edge(i32, i32);
   
   edge(x, y) <--
      node(x, neighbors),
      for &y in neighbors.iter(),
      if x != y;
}

否定和聚合

Ascent 支持分层否定和聚合。聚合器定义在 ascent::aggregators 中。您可以在其中找到 summinmaxcountmean

在以下示例中,学生的平均成绩存储在 avg_grade

use ascent::aggregators::mean;
type Student = u32;
type Course = u32;
type Grade = u16;
ascent! {
   relation student(Student);
   relation course_grade(Student, Course, Grade);
   relation avg_grade(Student, Grade);

   avg_grade(s, avg as Grade) <--
      student(s),
      agg avg = mean(g) in course_grade(s, _, g);
}

如果您提供的聚合器不足,可以定义自己的聚合器。例如,一个获取列中第二高值的聚合器的签名可以是以下这样

fn second_highest<'a, N: 'a>(inp: impl Iterator<Item = (&'a N,)>) -> impl Iterator<Item = N>
where N: Ord + Clone

聚合器甚至可以是参数化的!有关参数化聚合器的示例,请查看 ascent::aggregators 的定义。

宏定义

定义宏以扩展到主体项或头部项可能会有用。Ascent 允许您这样做。

您可以在 Ascent 宏 这里 了解更多关于宏的信息。

杂项

  • #![measure_rule_times] 会导致测量单个规则的执行时间。示例

    ascent! {
       #![measure_rule_times]
       // ...
    }
    let mut prog = AscentProgram::default();
    prog.run();
    println!("{}", prog.scc_times_summary());
    

    请注意,scc_times_summary() 为所有 Ascent 程序生成。带有 #![measure_rule_times] 它也报告单个规则的执行时间。

  • 带有 #![generate_run_timeout],将生成一个 run_timeout 函数,该函数在指定的时间后停止。

  • 功能 wasm-bindgen 允许 Ascent 程序在 WASM 环境中运行。

  • struct 声明可以添加到 ascent! 定义的最顶部。这允许更改生成的类型的名称和可见性,以及引入类型/生命周期参数和约束。

    ascent! {
       struct GenericTC<N: Clone + Eq + Hash>;
       relation edge(N, N);
       // ...
    }
    

    提示:如果您收到“在公共接口中私有类型 ... (错误 E0446)”警告,您可以通过使生成的 Ascent 类型私有来修复它,就像上面示例中做的那样。

依赖项

~3MB
~55K SLoC